From cf5680b52bdb1b7c3655a06250e115850b6eb63e Mon Sep 17 00:00:00 2001 From: Juha Heinanen Date: Mon, 8 Apr 2019 18:47:59 +0300 Subject: [PATCH] - Upgraded target Android API level to 28 (Android 9) - Moved some non-UI functionality from main activity to baresip service - Simplified implementation of call transfer - Modified baresip launcher and status bar images --- app/build.gradle | 17 +- app/src/main/AndroidManifest.xml | 4 +- app/src/main/cpp/baresip.c | 1 - .../com/tutpro/baresip/AccountListAdapter.kt | 5 +- .../com/tutpro/baresip/BaresipService.kt | 259 +++++++++---- .../main/kotlin/com/tutpro/baresip/Call.kt | 2 +- .../kotlin/com/tutpro/baresip/ChatActivity.kt | 6 - .../com/tutpro/baresip/ChatListAdapter.kt | 4 +- .../com/tutpro/baresip/ChatsActivity.kt | 10 +- .../kotlin/com/tutpro/baresip/MainActivity.kt | 360 ++++++++---------- .../com/tutpro/baresip/MessageListAdapter.kt | 5 +- .../main/kotlin/com/tutpro/baresip/Utils.kt | 31 +- app/src/main/res/drawable/baresip.png | Bin 738 -> 469 bytes app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2074 -> 2038 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1377 -> 1168 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 3028 -> 2913 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 5164 -> 4873 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 7696 -> 6066 bytes build.gradle | 3 +- 19 files changed, 409 insertions(+), 298 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 44b155d0..ea456f60 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,15 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion = 27 + compileSdkVersion = 28 defaultConfig { applicationId = 'com.tutpro.baresip' minSdkVersion 21 - targetSdkVersion 27 - versionCode = 49 - versionName = '6.1.0' + targetSdkVersion 28 + versionCode = 50 + versionName = '6.2.0' externalNativeBuild { cmake { cFlags '-DHAVE_INTTYPES_H' @@ -27,7 +28,7 @@ android { buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } @@ -43,14 +44,12 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.android.support:design:27.1.1' + implementation 'com.android.support:design:28.0.0' } repositories { mavenCentral() } - -apply plugin: 'kotlin-android-extensions' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 890c62dd..c1da457c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + @@ -95,6 +96,7 @@ @@ -102,7 +104,7 @@ android:name=".RunOnStartup" android:enabled="true" android:exported="true" - android:permission="android.permission.RECEIVE_BOOT_COMPLETED"> + android:permission="android.permission.RECEIVE_BOOT_COMPLETED" > diff --git a/app/src/main/cpp/baresip.c b/app/src/main/cpp/baresip.c index 669fd592..a03aee1d 100644 --- a/app/src/main/cpp/baresip.c +++ b/app/src/main/cpp/baresip.c @@ -977,7 +977,6 @@ Java_com_tutpro_baresip_Api_uag_1current_1set(JNIEnv *env, jobject thiz, jstring (*env)->ReleaseStringUTFChars(env, javaUA, native_ua); LOGD("running uag_current_set on %s\n", native_ua); uag_current_set(ua); - return; } JNIEXPORT jstring JNICALL diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountListAdapter.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountListAdapter.kt index 0187e265..44916fa9 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/AccountListAdapter.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/AccountListAdapter.kt @@ -41,8 +41,9 @@ class AccountListAdapter(private val cxt: Context, private val rows: ArrayList - if (Api.ua_isregistered(ua.uap)) Api.ua_unregister(ua.uap) - Api.ua_destroy(ua.uap) + if (Api.ua_isregistered(ua.uap)) + Api.ua_unregister(ua.uap) + // Api.ua_destroy(ua.uap) UserAgent.remove(ua) AccountsActivity.generateAccounts() AccountsActivity.saveAccounts() diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index 5bac460e..85cdd634 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -101,7 +101,8 @@ class BaresipService: Service() { action = "Start" Log.d(LOG_TAG, "Received onStartCommand with null intent") } else { - action = intent.getAction() + // Utils.dumpIntent(intent) + action = intent.action!! Log.d(LOG_TAG, "Received onStartCommand action $action") } @@ -162,6 +163,92 @@ class BaresipService: Service() { showStatusNotification() } + "Call Show", "Call Answer" -> { + val newIntent = Intent(this, MainActivity::class.java) + newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + newIntent.putExtra("action", action.toLowerCase()) + newIntent.putExtra("callp", intent!!.getStringExtra("callp")) + startActivity(newIntent) + } + + "Call Reject" -> { + val callp = intent!!.getStringExtra("callp") + val call = Call.find(callp) + if (call == null) { + Log.w(LOG_TAG, "onStartCommand did not find call $callp") + } else { + val peerUri = call.peerURI + val aor = call.ua.account.aor + Log.i(LOG_TAG, "Aor $aor rejected incoming call $callp from $peerUri") + Api.ua_hangup(call.ua.uap, callp, 486, "Rejected") + CallHistory.add(CallHistory(aor, peerUri, "in", false)) + CallHistory.save(filesPath) + } + } + + "Transfer Show", "Transfer Accept" -> { + val uap = intent!!.getStringExtra("uap") + val ua = UserAgent.find(uap) + if (ua == null) { + Log.w(LOG_TAG, "onStartCommand did not find ua $uap") + } else { + val newIntent = Intent(this, MainActivity::class.java) + newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + newIntent.putExtra("action", action.toLowerCase()) + newIntent.putExtra("callp", intent.getStringExtra("callp")) + newIntent.putExtra("uri", intent.getStringExtra("uri")) + startActivity(newIntent) + nm.cancel(BaresipService.TRANSFER_NOTIFICATION_ID) + } + } + + "Transfer Deny" -> { + val callp = intent!!.getStringExtra("callp") + val call = Call.find(callp) + if (call == null) + Log.w(LOG_TAG, "onStartCommand did not find call $callp") + else + Api.call_notify_sipfrag(callp, 603, "Decline") + nm.cancel(BaresipService.TRANSFER_NOTIFICATION_ID) + } + + "Message Show", "Message Reply" -> { + val newIntent = Intent(this, MainActivity::class.java) + newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + newIntent.putExtra("action", action.toLowerCase()) + newIntent.putExtra("uap", intent!!.getStringExtra("uap")) + newIntent.putExtra("time", intent.getStringExtra("time")) + startActivity(newIntent) + nm.cancel(BaresipService.MESSAGE_NOTIFICATION_ID) + } + + "Message Save" -> { + val uap = intent!!.getStringExtra("uap") + val ua = UserAgent.find(uap) + if (ua == null) + Log.w(LOG_TAG, "onStartCommand did not find UA $uap") + else + ChatsActivity.saveUaMessage(ua.account.aor, + intent.getStringExtra("time").toLong(), + applicationContext.filesDir.absolutePath) + nm.cancel(BaresipService.MESSAGE_NOTIFICATION_ID) + } + + "Message Delete" -> { + val uap = intent!!.getStringExtra("uap") + val ua = UserAgent.find(uap) + if (ua == null) + Log.w(LOG_TAG, "onStartCommand did not find UA $uap") + else + ChatsActivity.deleteUaMessage(ua.account.aor, + intent.getStringExtra("time").toLong(), + applicationContext.filesDir.absolutePath) + nm.cancel(BaresipService.MESSAGE_NOTIFICATION_ID) + } + "UpdateNotification" -> { updateStatusNotification() } @@ -243,14 +330,20 @@ class BaresipService: Service() { else status[account_index] = R.drawable.dot_green updateStatusNotification() + if (!Utils.isVisible()) + return } "registering failed" -> { status[account_index] = R.drawable.dot_red updateStatusNotification() + if (!Utils.isVisible()) + return } "unregistering" -> { status[account_index] = R.drawable.dot_yellow updateStatusNotification() + if (!Utils.isVisible()) + return } "call incoming" -> { val peerUri = Api.call_peeruri(callp) @@ -260,6 +353,8 @@ class BaresipService: Service() { CallHistory.add(CallHistory(aor, peerUri, "in", false)) CallHistory.save(filesPath) ua.account.missedCalls = true + if (!Utils.isVisible()) + return newEvent = "call rejected" } else { Log.d(LOG_TAG, "Incoming call $uap/$callp/$peerUri") @@ -267,44 +362,41 @@ class BaresipService: Service() { Utils.dtmfWatcher(callp))) startRinging() } - if ((newEvent == null) && !Utils.isVisible()) { - val intent = Intent(this, MainActivity::class.java) - .setAction(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_LAUNCHER) - val pi = PendingIntent.getActivity(this, CALL_REQ_CODE, intent, - 0) + if (!Utils.isVisible()) { + val intent = Intent(this, BaresipService::class.java) + intent.action = "Call Show" + intent.putExtra("callp", callp) + val pi = PendingIntent.getService(this, CALL_REQ_CODE, intent, + PendingIntent.FLAG_UPDATE_CURRENT) val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID) val caller = Utils.friendlyUri(ContactsActivity.contactName(peerUri), Utils.aorDomain(aor)) - val title = "Incoming call from $caller" nb.setSmallIcon(R.drawable.ic_stat) .setColor(ContextCompat.getColor(this, R.color.colorBaresip)) .setContentIntent(pi) .setAutoCancel(true) - .setContentTitle(title) + .setContentTitle("Incoming call from") + .setContentText(caller) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { nb.setVibrate(LongArray(0)) .setVisibility(VISIBILITY_PRIVATE) .setPriority(Notification.PRIORITY_HIGH) } - val answerIntent = Intent(this, MainActivity::class.java) - answerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) - answerIntent.putExtra("action", "answer") + val answerIntent = Intent(this, BaresipService::class.java) + answerIntent.action = "Call Answer" answerIntent.putExtra("callp", callp) - val answerPendingIntent = PendingIntent.getActivity(this, + val answerPendingIntent = PendingIntent.getService(this, ANSWER_REQ_CODE, answerIntent, PendingIntent.FLAG_UPDATE_CURRENT) - val rejectIntent = Intent(this, MainActivity::class.java) - rejectIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_SINGLE_TOP) - rejectIntent.putExtra("action", "reject") + val rejectIntent = Intent(this, BaresipService::class.java) + rejectIntent.action = "Call Reject" rejectIntent.putExtra("callp", callp) - val rejectPendingIntent = PendingIntent.getActivity(this, + val rejectPendingIntent = PendingIntent.getService(this, REJECT_REQ_CODE, rejectIntent, PendingIntent.FLAG_UPDATE_CURRENT) nb.addAction(R.drawable.ic_stat, "Answer", answerPendingIntent) nb.addAction(R.drawable.ic_stat, "Reject", rejectPendingIntent) nm.notify(CALL_NOTIFICATION_ID, nb.build()) + return } } "call established" -> { @@ -324,54 +416,68 @@ class BaresipService: Service() { am.mode = AudioManager.MODE_IN_COMMUNICATION requestAudioFocus(AudioManager.STREAM_VOICE_CALL) am.isSpeakerphoneOn = false + if (!Utils.isVisible()) + return + } + "call verified", "call secure" -> { + val call = Call.find(callp) + if (call == null) { + Log.e("Baresip", "Call $callp that is verified is not found") + return + } + if (ev[0] == "call secure") { + call.security = R.drawable.box_yellow + } else { + call.security = R.drawable.box_green + call.zid = ev[1] + } + if (!Utils.isVisible()) + return } "call transfer" -> { val call = Call.find(callp) if (call == null) { - Log.d(LOG_TAG, "AoR $aor call $callp to be transferred is not found") + Log.w(LOG_TAG, "Call $callp to be transferred is not found") return } if (!Utils.isVisible()) { - val intent = Intent(this, MainActivity::class.java) - .setAction(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_LAUNCHER) - val pi = PendingIntent.getActivity(this, TRANSFER_REQ_CODE, - intent, 0) + val intent = Intent(this, BaresipService::class.java) + intent.action = "Transfer Show" + intent.putExtra("uap", uap) + .putExtra("callp", callp) + .putExtra("uri", ev[1]) + val pi = PendingIntent.getService(this, TRANSFER_REQ_CODE, + intent, PendingIntent.FLAG_UPDATE_CURRENT) val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID) val target = Utils.friendlyUri(ContactsActivity.contactName(ev[1]), Utils.aorDomain(aor)) - val title = "Call transfer request to $target" nb.setSmallIcon(R.drawable.ic_stat) - .setColor(ContextCompat.getColor(this, - R.color.colorBaresip)) + .setColor(ContextCompat.getColor(this, R.color.colorBaresip)) .setContentIntent(pi) .setDefaults(Notification.DEFAULT_SOUND) .setAutoCancel(true) - .setContentTitle(title) + .setContentTitle("Call transfer request to") + .setContentText(target) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { nb.setVibrate(LongArray(0)) .setVisibility(VISIBILITY_PRIVATE) .setPriority(Notification.PRIORITY_HIGH) } - val acceptIntent = Intent(this, MainActivity::class.java) - acceptIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) - acceptIntent.putExtra("action", "transfer") + val acceptIntent = Intent(this, BaresipService::class.java) + acceptIntent.action = "Transfer Accept" acceptIntent.putExtra("uap", uap) - acceptIntent.putExtra("callp", callp) - acceptIntent.putExtra("uri", ev[1]) - val acceptPendingIntent = PendingIntent.getActivity(this, + .putExtra("callp", callp) + .putExtra("uri", ev[1]) + val acceptPendingIntent = PendingIntent.getService(this, ACCEPT_REQ_CODE, acceptIntent, PendingIntent.FLAG_UPDATE_CURRENT) - val denyIntent = Intent(this, MainActivity::class.java) - denyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_SINGLE_TOP) - denyIntent.putExtra("action", "deny") + val denyIntent = Intent(this, BaresipService::class.java) + denyIntent.action = "Transfer Deny" denyIntent.putExtra("callp", callp) - val denyPendingIntent = PendingIntent.getActivity(this, + val denyPendingIntent = PendingIntent.getService(this, DENY_REQ_CODE, denyIntent, PendingIntent.FLAG_UPDATE_CURRENT) nb.addAction(R.drawable.ic_stat, "Accept", acceptPendingIntent) nb.addAction(R.drawable.ic_stat, "Deny", denyPendingIntent) - nm.notify(CALL_NOTIFICATION_ID, nb.build()) + nm.notify(TRANSFER_NOTIFICATION_ID, nb.build()) return } } @@ -395,6 +501,12 @@ class BaresipService: Service() { if (am.isSpeakerphoneOn) am.isSpeakerphoneOn = false if (audioFocused) abandonAudioFocus() } + if (speakerPhone) { + am.isSpeakerphoneOn = !am.isSpeakerphoneOn + speakerPhone = am.isSpeakerphoneOn + } + if (!Utils.isVisible()) + return } "transfer failed" -> { Log.d(LOG_TAG, "AoR $aor hanging up call $callp with ${ev[1]}") @@ -406,6 +518,8 @@ class BaresipService: Service() { } if (newEvent == null) newEvent = event val intent = Intent("service event") + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) intent.putExtra("event", newEvent) intent.putExtra("params", arrayListOf(uap, callp)) LocalBroadcastManager.getInstance(this).sendBroadcast(intent) @@ -413,9 +527,9 @@ class BaresipService: Service() { @Keep fun messageEvent(uap: String, peer: String, msg: ByteArray) { - var s = "Decoding of message failed!" + var text = "Decoding of message failed!" try { - s = String(msg, StandardCharsets.UTF_8) + text = String(msg, StandardCharsets.UTF_8) } catch (e: Exception) { Log.e(LOG_TAG, "UTF-8 decode failed") } @@ -426,14 +540,15 @@ class BaresipService: Service() { return } val timeStamp = System.currentTimeMillis().toString() + Message.add(Message(ua.account.aor, peer, text, timeStamp.toLong(), + R.drawable.arrow_down_green, 0, "", true)) if (!Utils.isVisible()) { - val intent = Intent(this, MainActivity::class.java) - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) - .putExtra("action", "show") - .putExtra("uap", uap) - .putExtra("peer", peer) - val pi = PendingIntent.getActivity(this, MESSAGE_REQ_CODE, intent, 0) + val intent = Intent(this, BaresipService::class.java) + intent.action = "Message Show" + intent.putExtra("uap", uap) + .putExtra("time", timeStamp) + val pi = PendingIntent.getService(this, MESSAGE_REQ_CODE, intent, + PendingIntent.FLAG_UPDATE_CURRENT) val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID) val sender = Utils.friendlyUri(ContactsActivity.contactName(peer), Utils.aorDomain(ua.account.aor)) @@ -443,44 +558,41 @@ class BaresipService: Service() { .setDefaults(Notification.DEFAULT_SOUND) .setAutoCancel(true) .setContentTitle("Message from $sender") - .setContentText(s) + .setContentText(text) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { nb.setVibrate(LongArray(0)) .setVisibility(VISIBILITY_PRIVATE) .setPriority(Notification.PRIORITY_HIGH) } - 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") + val replyIntent = Intent(this, BaresipService::class.java) + replyIntent.action = "Message Reply" replyIntent.putExtra("uap", uap) - replyIntent.putExtra("peer", peer) - val replyPendingIntent = PendingIntent.getActivity(this, + .putExtra("time", timeStamp) + val replyPendingIntent = PendingIntent.getService(this, REPLY_REQ_CODE, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT) - val saveIntent = Intent(this, MainActivity::class.java) - saveIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_SINGLE_TOP) - saveIntent.putExtra("action", "save") + val saveIntent = Intent(this, BaresipService::class.java) + saveIntent.action = "Message Save" saveIntent.putExtra("uap", uap) - saveIntent.putExtra("time", timeStamp) - val savePendingIntent = PendingIntent.getActivity(this, + .putExtra("time", timeStamp) + val savePendingIntent = PendingIntent.getService(this, SAVE_REQ_CODE, saveIntent, 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") + val deleteIntent = Intent(this, BaresipService::class.java) + deleteIntent.action = "Message Delete" deleteIntent.putExtra("uap", uap) - deleteIntent.putExtra("time", timeStamp) - val deletePendingIntent = PendingIntent.getActivity(this, + .putExtra("time", timeStamp) + val deletePendingIntent = PendingIntent.getService(this, DELETE_REQ_CODE, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT) nb.addAction(R.drawable.ic_stat, "Reply", replyPendingIntent) nb.addAction(R.drawable.ic_stat, "Save", savePendingIntent) nb.addAction(R.drawable.ic_stat, "Delete", deletePendingIntent) nm.notify(MESSAGE_NOTIFICATION_ID, nb.build()) + return } val intent = Intent("service event") - intent.putExtra("event", "message") - intent.putExtra("params", arrayListOf(uap, peer, s, timeStamp)) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + intent.putExtra("event", "message show") + intent.putExtra("params", arrayListOf(uap, timeStamp)) LocalBroadcastManager.getInstance(this).sendBroadcast(intent) } @@ -645,7 +757,8 @@ class BaresipService: Service() { val STATUS_NOTIFICATION_ID = 101 val CALL_NOTIFICATION_ID = 102 - val MESSAGE_NOTIFICATION_ID = 103 + val TRANSFER_NOTIFICATION_ID = 103 + val MESSAGE_NOTIFICATION_ID = 104 val STATUS_REQ_CODE = 1 val CALL_REQ_CODE = 2 diff --git a/app/src/main/kotlin/com/tutpro/baresip/Call.kt b/app/src/main/kotlin/com/tutpro/baresip/Call.kt index 54a7e26f..27ea79be 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Call.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Call.kt @@ -1,6 +1,7 @@ package com.tutpro.baresip import android.text.TextWatcher +import android.util.Log import java.util.ArrayList class Call(val callp: String, val ua: UserAgent, val peerURI: String, val dir: String, @@ -49,6 +50,5 @@ class Call(val callp: String, val ua: UserAgent, val peerURI: String, val dir: S if (c.callp == callp) return c return null } - } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt index 60e6d2d2..458d5baa 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt @@ -219,12 +219,6 @@ class ChatActivity : AppCompatActivity() { return true } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { - if ((requestCode == MainActivity.CONTACT_CODE) && (resultCode == Activity.RESULT_OK)) { - setTitle("Chat with ${data.getStringExtra("name")}") - } - } - private fun uaPeerMessages(aor: String, peerUri: String): ArrayList { val res = ArrayList() for (m in Message.messages()) diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatListAdapter.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatListAdapter.kt index 0fcbb818..75d043c8 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatListAdapter.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatListAdapter.kt @@ -65,10 +65,8 @@ class ChatListAdapter(private val cxt: Context, private var rows: ArrayList { + if (!Call.calls().isEmpty()) { + Toast.makeText(applicationContext, "You already have an active call!", + Toast.LENGTH_SHORT).show() + return + } + val uap = intent.getStringExtra("uap") + val ua = UserAgent.find(uap) + if (ua == null) { + Log.e("Baresip", "handleIntent 'call' did not find ua $uap") + return + } + if (ua != UserAgent.uas()[aorSpinner.selectedItemPosition]) + spinToAor(ua.account.aor) + resumeAction = action + resumeUri = intent.getStringExtra("peer") + } + "call show", "call answer" -> { + val callp = intent.getStringExtra("callp") + val call = Call.find(callp) + if (call == null) { + Log.e("Baresip", "handleIntent '$action' did not find call $callp") + return + } + val ua = call.ua + if (ua != UserAgent.uas()[aorSpinner.selectedItemPosition]) + spinToAor(ua.account.aor) + resumeAction = action + resumeCall = call + } + "transfer show", "transfer accept" -> { + val callp = intent.getStringExtra("callp") + val call = Call.find(callp) + if (call == null) { + Log.e("Baresip", "handleIntent '$action' did not find call $callp") + moveTaskToBack(true) + return + } + resumeAction = action + resumeCall = call + resumeUri = intent.getStringExtra("uri") + } + "message show", "message reply" -> { + val uap = intent.getStringExtra("uap") + val ua = UserAgent.find(uap) + if (ua == null) { + Log.e("Baresip", "onNewIntent did not find ua $uap") + return + } + if (ua != UserAgent.uas()[aorSpinner.selectedItemPosition]) + spinToAor(ua.account.aor) + resumeAction = action + resumeUap = uap + resumeTime = intent.getStringExtra("time") + } } } + override fun onResume() { + super.onResume() + Log.d("Baresip", "Main resumed with action '$resumeAction'") + // imm.hideSoftInputFromWindow(callUri.windowToken, 0) + visible = true + when (resumeAction) { + "call show" -> + handleServiceEvent("call incoming", + arrayListOf(resumeCall!!.ua.uap, resumeCall!!.callp)) + "call answer" -> + answerButton.performClick() + "call reject" -> + rejectButton.performClick() + "call" -> + callButton.performClick() + "transfer show", "transfer accept" -> + handleServiceEvent("$resumeAction,$resumeUri", + arrayListOf(resumeCall!!.ua.uap, resumeCall!!.callp)) + "message show", "message reply" -> + handleServiceEvent(resumeAction, arrayListOf(resumeUap, resumeTime)) + else -> { + uaAdapter.notifyDataSetChanged() + if (aorSpinner.selectedItemPosition != -1) + showCall(UserAgent.uas()[aorSpinner.selectedItemPosition]) + } + } + resumeAction = "" + } + + override fun onPause() { + super.onPause() + // Log.d("Baresip", "Main paused") + visible = false + } + private fun handleServiceEvent(event: String, params: ArrayList) { + if (taskId == -1) { + Log.d("Baresip", "Omit service event '$event' for task -1") + return + } if (event == "stopped") { Log.d("Baresip", "Handling service event 'stopped'") quitTimer.cancel() finishAndRemoveTask() - System.exit(0) + // System.exit(0) return } val uap = params[0] @@ -491,11 +599,10 @@ class MainActivity : AppCompatActivity() { dtmf.visibility = View.INVISIBLE infoButton.visibility = View.INVISIBLE } - if (Utils.isVisible()) { - Log.d("Baresip", "Baresip is visible") - val i = Intent(applicationContext, MainActivity::class.java) - i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) - startActivity(i) + if (!visible) { + val i = Intent(applicationContext, MainActivity::class.java) + i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) + startActivity(i) } } "call established" -> { @@ -528,10 +635,10 @@ class MainActivity : AppCompatActivity() { dtmf.hint = "DTMF" dtmf.visibility = View.VISIBLE dtmf.requestFocus() + (dtmf.tag as ArrayList).add(call.dtmfWatcher!!) + dtmf.addTextChangedListener(call.dtmfWatcher) infoButton.visibility = View.VISIBLE } - (dtmf.tag as ArrayList).add(call.dtmfWatcher!!) - dtmf.addTextChangedListener(call.dtmfWatcher) } "call verify" -> { val callp = params[1] @@ -570,7 +677,7 @@ class MainActivity : AppCompatActivity() { } dialog.dismiss() } - verifyDialog.create().show() + if (!isFinishing()) verifyDialog.create().show() } "call verified", "call secure" -> { val callp = params[1] @@ -580,20 +687,16 @@ class MainActivity : AppCompatActivity() { return } val tag: String - if (ev[0] == "call secure") { - call.security = R.drawable.box_yellow + if (call.security == R.drawable.box_yellow) tag = "yellow" - } else { - call.security = R.drawable.box_green + else tag = "green" - call.zid = ev[1] - } if (ua == UserAgent.uas()[aorSpinner.selectedItemPosition]) { securityButton.setImageResource(call.security) securityButton.tag = tag } } - "call transfer" -> { + "call transfer", "transfer show" -> { val callp = params[1] val call = Call.find(callp) if (call == null) { @@ -606,9 +709,9 @@ class MainActivity : AppCompatActivity() { transferDialog.setMessage("Do you accept to transfer call to $target?") transferDialog.setPositiveButton("Yes") { dialog, _ -> if (call in Call.calls()) - transfer(ua, call, ev[1]) - else - call(ua, ev[1]) + Api.ua_hangup(uap, callp, 0, "") + call(ua, ev[1], "transferring") + showCall(ua) dialog.dismiss() } transferDialog.setNegativeButton("No") { dialog, _ -> @@ -618,6 +721,18 @@ class MainActivity : AppCompatActivity() { } transferDialog.create().show() } + "transfer accept" -> { + val callp = params[1] + val call = Call.find(callp) + if (call == null) { + Log.e("Baresip", "Call $callp to be transferred is not found") + return + } + if (call in Call.calls()) + Api.ua_hangup(uap, callp, 0, "") + call(ua, ev[1], "transferring") + showCall(ua) + } "call closed" -> { val watchers = dtmf.tag as ArrayList if (watchers.size > 0) { @@ -630,10 +745,6 @@ class MainActivity : AppCompatActivity() { callsButton.setImageResource(R.drawable.calls_missed) } speakerIcon.setIcon(R.drawable.speaker_off) - if (BaresipService.speakerPhone) { - baresipService.setAction("ToggleSpeaker") - startService(baresipService) - } val param = ev[1].trim() if ((param != "") && (Call.uaCalls(ua, "").size == 0)) { if (param.get(0).isDigit()) @@ -644,27 +755,25 @@ class MainActivity : AppCompatActivity() { Toast.LENGTH_LONG).show() } } - "message" -> { - val peerUri = params[1] - val msgText = params[2] - val time = params[3] - Log.d("Baresip", "Incoming message $aor/$peerUri/$msgText") - Message.add(Message(aor, peerUri, msgText, time.toLong(), - R.drawable.arrow_down_green, 0, "", - true)) - if (Utils.isVisible()) { - if ((aorSpinner.selectedItemPosition == -1) || - (ua != UserAgent.uas()[aorSpinner.selectedItemPosition])) - aorSpinner.setSelection(account_index) - val i = Intent(applicationContext, ChatsActivity::class.java) - i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - val b = Bundle() - b.putString("aor", ua.account.aor) - b.putString("peer", peerUri) - b.putBoolean("focus", false) - i.putExtras(b) - startActivity(i) + "message show", "message reply" -> { + val timeStamp = params[1].toLong() + val msg = ChatsActivity.findUaMessage(ua.account.aor, timeStamp) + if (msg == null) { + Log.e("Baresip", "Message $aor/$timeStamp is not found") + return } + Log.d("Baresip", "Message for $aor from ${msg.peerUri}") + if ((aorSpinner.selectedItemPosition == -1) || + (ua != UserAgent.uas()[aorSpinner.selectedItemPosition])) + aorSpinner.setSelection(account_index) + val i = Intent(applicationContext, ChatsActivity::class.java) + i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + val b = Bundle() + b.putString("aor", aor) + b.putString("peer", msg.peerUri) + b.putBoolean("focus", ev[0] == "message reply") + i.putExtras(b) + startActivity(i) } "mwi notify" -> { val lines = ev[1].split("\n") @@ -691,143 +800,11 @@ class MainActivity : AppCompatActivity() { } } - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - val action = intent.getStringExtra("action") - Log.d("Baresip", "onNewIntent action '$action'") - if (action != null) handleIntent(intent) - } - - private fun handleIntent(intent: Intent) { - val action = intent.getStringExtra("action") - Log.d("Baresip", "Handling intent '$action'") - when (action) { - "call" -> { - if (!Call.calls().isEmpty()) { - Toast.makeText(applicationContext, "You already have an active call!", - Toast.LENGTH_SHORT).show() - return - } - val uap = intent.getStringExtra("uap") - val ua = UserAgent.find(uap) - if (ua == null) { - Log.e("Baresip", "handleIntent 'call' did not find ua $uap") - return - } - if (ua != UserAgent.uas()[aorSpinner.selectedItemPosition]) - spinToAor(ua.account.aor) - makeCall = intent.getStringExtra("peer") - } - "answer" -> { - answerCall = intent.getStringExtra("callp") - } - "reject" -> { - rejectCall = intent.getStringExtra("callp") - moveTaskToBack(true) - } - "transfer" -> { - val callp = intent.getStringExtra("callp") - val call = Call.find(callp) - val uap = intent.getStringExtra("uap") - val ua = UserAgent.find(uap) - if (ua == null) { - Log.e("Baresip", "Ua $uap of call transfer target does not exist") - moveTaskToBack(true) - return - } - if (call == null) - call(ua, intent.getStringExtra("uri")) - else - transfer(ua, call, intent.getStringExtra("uri")) - } - "deny" -> { - val callp = intent.getStringExtra("callp") - val call = Call.find(callp) - if (call == null) - Log.d("Baresip", "Call $callp denied transfer does not exist anymore") - else - Api.call_notify_sipfrag(callp, 603, "Decline") - moveTaskToBack(true) - } - "show", "reply", "save", "delete" -> { - val uap = intent.getStringExtra("uap") - val ua = UserAgent.find(uap) - if (ua == null) { - Log.e("Baresip", "onNewIntent did not find ua $uap") - return - } - val aor = ua.account.aor - when (action) { - "show", "reply" -> { - val i = Intent(this@MainActivity, ChatsActivity::class.java) - val b = Bundle() - if (ua != UserAgent.uas()[aorSpinner.selectedItemPosition]) - spinToAor(aor) - b.putString("aor", aor) - b.putString("peer", intent.getStringExtra("peer")) - b.putBoolean("focus", action == "reply") - i.putExtras(b) - startActivityForResult(i, MESSAGES_CODE) - } - "save" -> { - ChatsActivity.saveUaMessage(ua.account.aor, - intent.getStringExtra("time").toLong(), - applicationContext.filesDir.absolutePath) - moveTaskToBack(true) - } - "delete" -> { - ChatsActivity.deleteUaMessage(ua.account.aor, - intent.getStringExtra("time").toLong(), - applicationContext.filesDir.absolutePath) - moveTaskToBack(true) - } - } - nm.cancel(BaresipService.MESSAGE_NOTIFICATION_ID) - } - "message" -> { - val ua = UserAgent.find(intent.getStringExtra("uap"))!! - val i = Intent(applicationContext, ChatsActivity::class.java) - val b = Bundle() - b.putString("aor", ua.account.aor) - b.putString("peer", intent.getStringExtra("peer")) - b.putBoolean("focus", true) - i.putExtras(b) - startActivity(i) - } - } - } - - override fun onPause() { - super.onPause() - // Log.d("Baresip", "Main paused") - visible = false - } - /*override fun onStop() { super.onStop() Log.d("Baresip", "Main stopped") }*/ - override fun onResume() { - super.onResume() - Log.d("Baresip", "Main resumed") - imm.hideSoftInputFromWindow(callUri.windowToken, 0) - visible = true - if (answerCall != "") { - answerCall = "" - answerButton.performClick() - } - if (rejectCall != "") { - rejectCall = "" - rejectButton.performClick() - } - if (makeCall != "") { - callUri.setText(makeCall) - makeCall = "" - callButton.performClick() - } - } - override fun onBackPressed() { moveTaskToBack(true) } @@ -976,7 +953,7 @@ class MainActivity : AppCompatActivity() { } } - private fun call(ua: UserAgent, uri: String) { + private fun call(ua: UserAgent, uri: String, status: String) { if (ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_DENIED) { Toast.makeText(applicationContext, @@ -991,8 +968,7 @@ class MainActivity : AppCompatActivity() { val callp = Api.ua_connect(ua.uap, uri) if (callp != "") { Log.d("Baresip", "Adding outgoing call ${ua.uap}/$callp/$uri") - Call.calls().add(Call(callp, ua, uri, "out", "outgoing", - Utils.dtmfWatcher(callp))) + Call.calls().add(Call(callp, ua, uri, "out", status, Utils.dtmfWatcher(callp))) imm.hideSoftInputFromWindow(callUri.windowToken, 0) securityButton.visibility = View.INVISIBLE callButton.visibility = View.INVISIBLE @@ -1010,6 +986,7 @@ class MainActivity : AppCompatActivity() { } } + // Currently transfer is implemented by first closing existing call and the making the new one private fun transfer(ua: UserAgent, call: Call, uri: String) { val newCallp = Api.ua_call_alloc(ua.uap, call.callp) if (newCallp != "") { @@ -1017,7 +994,8 @@ class MainActivity : AppCompatActivity() { val newCall = Call(newCallp, ua, uri, "out", "transferring", Utils.dtmfWatcher(newCallp)) Call.calls().add(newCall) - Api.call_stop_audio(call.callp) + Api.ua_hangup(ua.uap, call.callp, 0, "") + // Api.call_stop_audio(call.callp) val err = Api.call_connect(newCallp, uri) if (err == 0) { Api.call_start_audio(newCallp) @@ -1103,15 +1081,6 @@ class MainActivity : AppCompatActivity() { holdButton.visibility = View.INVISIBLE dtmf.visibility = View.INVISIBLE infoButton.visibility = View.INVISIBLE - if (answerCall == call.callp) { - answerCall = "" - answerButton.performClick() - } - if (rejectCall == call.callp) { - rejectCall = "" - rejectButton.performClick() - moveTaskToBack(true) - } } "connected" -> { securityButton.setImageResource(call.security) @@ -1141,10 +1110,13 @@ class MainActivity : AppCompatActivity() { companion object { - var visible = true - var makeCall = "" - var answerCall = "" - var rejectCall = "" + var visible = false + + var resumeAction = "" + var resumeUap = "" + var resumeCall: Call? = null + var resumeUri = "" + var resumeTime = "" const val ACCOUNTS_CODE = 1 const val CONTACTS_CODE = 2 diff --git a/app/src/main/kotlin/com/tutpro/baresip/MessageListAdapter.kt b/app/src/main/kotlin/com/tutpro/baresip/MessageListAdapter.kt index e7cde52a..74d94cb2 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MessageListAdapter.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MessageListAdapter.kt @@ -63,7 +63,10 @@ class MessageListAdapter(private val cxt: Context, private val rows: ArrayList"))) + u = uri.substring(1).substringBeforeLast(">") + if (u.contains("@") && !u.substring(4).contains(":") && + !u.contains(";")) { + val user = uriUserPart(u) + val host = uriHostPart(u) if (host == domain) return user else return "$user@$host" } else { return uri @@ -281,4 +286,22 @@ object Utils { } } + fun dumpIntent(intent: Intent) { + + val bundle: Bundle = intent.extras ?: return + + val keys = bundle.keySet() + val it = keys.iterator() + + Log.d("Baresip", "Dumping intent start") + + while (it.hasNext()) { + val key = it.next() + Log.d("Baresip","[" + key + "=" + bundle.get(key)+"]"); + } + + Log.d("Baresip", "Dumping intent finish") + + } + } diff --git a/app/src/main/res/drawable/baresip.png b/app/src/main/res/drawable/baresip.png index 2d3723e8e1bdb0f54e572e7ea9ec5e22a5ea6325..984ca1a2c605c784e63e2072f7126c8270fc91f2 100644 GIT binary patch delta 398 zcmV;90dfA~1=RzPQGeqE2oD4_BD6a%0004ENkl4@?GdmId`WE|fqkPy#}T7+1ubA5 zI0ViC1+=BMD_P0&R`6hy*meH3)HVW6fGer(5)h>U<*DEz(f3>6184v{RkSEfXypy) zy3k(Mz{}9|6MyJCe>O#=r2%4peMoI5hb^>T)c}G2zEPrEwUWny2^YGlV#vt})`6Py zn`MXm4jfBuYaV5r&c6fhvv$%TC6Br57O)09OKqQkMPLRv1rCf7{aoK@~iaDDhtY9~Q^)y}yXpMBvO!Ns@$T)AHwES#)T>vFydS_aeUG6j>T(6DUcN zD3{A+r>3TSu~-ZM4Knoh_GSQ1>@$1NgUYjkg;|3Pn5FtPL*3c#?(WRo+}x_;4Ft|P z%!~u=*wv$E{eMNnxrS)hGyWJipjjdXNU)ZmX?|<#*$lz9;r}6!Qn`E(<&|`7E$rLX zD#6=DG58K%euX%QVjiv^h8xF?5VjD38Mrp%+{1`B(h+D7W(hF@^2rWqz z5;5FwoGYf@W+S-Y=crcU(YSc>L0im$ppb7={A?#sg@0_$7#x*F(RNpnfy;_0GTzKU z2)6xYnv2pL;b3e*+fU%EMqu!xx|0V@(9yO71eIWE!wPV0k8SOs{fQM1Nd#0yPbc6u zmjkPvv06)o&&!P;c=}Q7-EAZlap#oD`xYdURKJ@A2t$=h;P7s^@LZG@kaKAIr*!)h z%zqEuJb!7N=`z-~>_VZSbtM`Z8Ig025Q2~}a2**mcF{C41AW7?s9l!L7`a$_c-nO3 z2zZ}5WoArFOoYkF$>x2qR4P4+<5+PVOGzyGSe5!b#G3yVYq^|0udl1y=;-LsMwfp5 z_ahrcQCB{nw-#=!)ptuvOOo_OQgvu(h=G9t02Y1&5z}5k^&$?W00000NkvXXu0mjf DvFS`M diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index 4a292e73b64c31b9fe2afb6ef4bc8f7c457aa131..99533c81677b50b40b00718307873eb310db007e 100644 GIT binary patch delta 2025 zcmVh-%2ZKpOK~#9!?VMd~99I>`f9KxW_1cad+jY`3-w7hL zX<9(yp%n#02r5FNP*hQgkB0(EJT7&FR0$P`x<{lnq$MM?Z)RDcA&Fqmz@~(I1<2UF0?>*<-S!i0* zE={|2&jtT2`LH1_Ll*Pp%zY!GHrwf1I;=wwQ3Dtd3wXHDV59|=E)iL!sv8j53@lgQ zez6e%d5ih7%YU}_V7$kSHN7stXh;^J4WSdE1+kzrZ%57jNC65u^q0e z2*=LAOXF~M6pVpN8Biz|AqZT3YkpgInFgEAgRkv^I;KaHOAn1pbC{rt^TNQr4ay`CEZzmT4bJt2#n`N zV%?~=q^Iuu>4+Uk9@*0;((|iR6;2#tO%F<yy_0h^8j}xE z-!?uVkqg&@?EZ1Wflk;nvXZv0+wtsxWNnw1#kdM4tSB0wc$u=~!&iaBz|YjTrOU#D zoQt+Z8+cY+rmd^}$HhI%I&%J&&BC@TA!vumvwv`|481+VxwG)+mxa+YdgW9}ZvYNS zKHLZFRo}i|Pml8cpK{_|{hpo6yXCvvu5Rtgm0@&LdEkUn87C?RSE~UX-NKgDuyM68 zRuqo^T>;%K!tIg|_u=@y`nE2u+IrQb^FW_5hORD{nu1a(D%@)SiZdG^tS^?Bp=$P^rE^~ z6yFBUa{`~xUG}ds6e5R`WhA~}_3eJ(s0b7aiYm0V)m*~Oz_+>v1HAjSF+3>tqJ+87 z7_i$_M-@UQhob78M=sueSZ{c z&jA05rVvUHKqezIn*z&$9|D+6g8`alX8<%IrS7uctPf7txbIFnchV1!;#2h3iuXDEHJPKna+LL(G^s z->U)Cj`E?@0h-#;O4WPDVnEI%CV#~l;ClOjO6o!kr~s$p@QPQEBupi)aTS+KBOnKq zlP0FK?43Ys8UX=>vnQbmLcIlOw?i&2uNmTrtv%|f>T3iqMF#I5>N)B94sE@6TIhdKrxMgEYJZmUFw*p|NjE2 zsRWb-)&R7$RI^t%=ZPW(PH4Xuq!myu&cn|4|7!phfj=$wK#dBh2gn&Cw6)FqJlaY1 z?e9|xNNjOis>$Fs#0L z2!PMXr4PutxPMi1vL6<{fc7uEccuLqlG$IVhFJNNs^+%tZeit0IC)YLsoC!^aF6<#l52$JFRGD7T;Pf49^fg-hd0i* zWW^kq#0_*H`zNkdXCGX#LNYhr0LPDOytZgs(?sMW>f8S;h#WUbJq)%W6TXS=bJYeuH6bpo`k7Moted-7L^Y; z-@I&w6xSz>0Vu%^qk6CQd!gjRao`}}86z0OeZZGPllh|?bpOPmtsQR7rWNx3YgT&K zwRFJ0--JgWS1OenIKKhzbp7Tj4 zQ--2ZMt%}nO6&)21FlfT;)q&*E81~d}8SQP%H<-9t^hv?*iUY4Sz?}ARGo>1CGM!XVhO6^~CZ4>ep8#qz-UM z8e--C0Nv>=)B|(Xb5lUG)|5*-&yVMlub_U0rZuf;4R8MiKu!EdDR&=n00000NkvXX Hu0mjf8TH8& delta 2062 zcmV+p2=Vv!51J5=BYy|sNkl-&YZru7kDHCUV2lGY!! zEhSC*qqe4rY0@$hP+JDY)W2oZxq z*j9(bai+4eGS+X9+d*0FN4gg0J;4l z$hH4~Tv`nBn|~sZ>(p!Pq=Ri_TZIsG^XAPIeOo#bj&md^DJkixu6d1r6~3@KWcs-# z*(bG$B2s|D*8z1~E1oFt?F0U0WjlqQ_A_j&Y6IQAef!4p<;!FBZR*hQDlIJ?qb}Is zGQI*mR(=R-k!Dkrq`Uecyoei47_KG*Zva&jk?a0oB&l zHYO)0Pw^X|?Ck89eE^ca!iYnz|4yWrvXEje=zRaYJ%|CVTD5Ab-vH(2=1w#Ms&}Bg z`T%9cJ%5zNH7I{A136%m2U7G#q#oW}| zmmH|_0P4L}1Efy@Ob;mkss~WN?MS`0gaCyAg#d*Bg#d*Bg#d*Bg#d*Bg#d*Bg#ZOA zAOR>Z)d_j>7RcX~j)T|0`MRc|3j=y#$KwGh+kfS$8X>S9>OROX4mZ=30-#0ZNJ*tgvyTEz$^#mk4K(8j z&^xD)-YP-rXVJbNlPc)*gFw8B*`)v(*Q5`r@EK-NUULL#<{_XS>$U$4*(_*Arl8qY zL4Pl9)c)3UGf@0Vq}L96^zgFoZ{^T+`k?Nq9xv*z8XlFR{Xcx2_L{xoZyQQ`vuY4( z@}LQwMaKRvP=hjoUOB|NG(C~(d&^27xB+oRgI5)KT{ZrwclK{qYTCaDz8% zpYO8`Xvl7lCxt8dI+F(7xSv5Zlkc*h>ZfH=JLfVP~6b`RGXmpnyE zE+-kQB|jVo3SvN>Uz-F~AAgH=N@BooBHn+k=dNdVi?Q`eCz40r4W5eyxdh^E}Tujk(oeQb0Vh)rVMX zfb=7oX%a=2BR6~l((7kARt@G%6pvP;&k!>L+Fu3c46)V#8AI$?wMqLReTY2{P$2a| z&jk?c8?dW&K(>b_ALiW~)j1b3=0VY^g6w9^gYsNEXnDDV?*Z{O)tIu&#DAG|rfXF( zG7V_3RRd&Pt^Bmm#B;Ibr!?KX%)$487^}7{uKUr_mPbH|5X-~4Q#CJ}_27mUR{y~Jk;y4TdUvh$aRwcKas zNu&`u-rhKLi`U2%T!8fTbC%c*+EDC%|8@>|&6|9|Sz0V)%m$!Y3V%*q0XoCX{r$em z6OUF0$o3bj)!HsVM&YpHWr)?Ko}C&Dk+;BXI}g%p9a%}~ZIBD|1P!qQEi6Zxknat9 z;3h$bt_Sh{AieNHI+twRjbCvZWDn;iDlK%Pl!px33iNIs&_`;j@MgZCgkwlwRUjpn zqVtw>V|e~s8(>@TTz?+)u)Tn|+PTulCMPc8jJJqK_uco}0yHug=#Aq@@1Fpgt3dVJ z>J5hTpyV=;`q_G00o}QC$L=pc&c`?`Uc7jq!{MmbKbpdqoVbaSHyOC)#}lpZi{`v# zjhZ|0zQooZWx;!i7AnuOTmX5$sr~tr5W)$ls;cVNf&~kP>wnwSk&yU-Aic}W%Pst% z5aef?s0+Iu9@0XZg-2TZjPdg|4S@Tu2$^w7lblK+EAIl@o7<+tm#~d&Yhz<06&4m+ zxeb1KIF!pJqZ1Mmre0Eq-EOB~IoQ_X;^NEk@$oZM8)7mtGW_%OtR+j9M5@!E>C>mr zEhs3+R-9}#HGegBo6Xj!&H*JZo9t|(8eDRwO`A54ZC4={73R-F!Re_^LzPU5i;J6= zn3(usQc_Z)`y2?Ow~1|JTiNExlPC8I>x84*oH=u1#*ZIAP`w_eWORVbFt*k8!*=Qu s@xs+bBN-N7JP^fY8+Qv&J*58+%cSy7G(KS(00000Ne4wvM6N<$g1VUc6aWAK diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index c917693cac063eea6e393aabfa2d40da416c5776..c30a1e0293027ecbc19e60afc05c0c74787655c1 100644 GIT binary patch delta 1148 zcmV-?1cUqG3XlnqBYyw}VoOIv0RI600RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_ z000McNliru;{*r~DljhI_8I^H1RY64K~!ko?U_wzTvZguf9KveGs&bigigppLQ)k< zOBc2Tp|Q5Qk**XJq~b#Sz~;3Cts7BL6hEjS)=<*1B}xMd3V*Jo8^H!!q(!8XwuoXd zme!DjVA?bvGfm#R$Hg?0nS7B<>wHMga^}66^SkGN&OPT|ShIYSWLVt)Tj_(|N5jpo zx^{#crWzdgp(&>YN|8>^-}P2=|2bHKSApa~JJ2}3Dq(J(90|GwyAAAb)JjZ*#qOIV-^kx1nNWTYe&4p3@a z16TuC16TuC+HA*I`80Ed>;qvs+RBH)%1LA;J{^RrZi(S4*qm>HrK6J4E;sco-*t>j6_5fQbnR0#J3i zzkCA4SAXB8^WSsNtLkU<^@damCMO-hKfpEub5+}=cuAbEf6ZN$*R{<)Cun-XVC0+6 zMd%zEaaaqUCjc9OGukegJO#j>l=EXnX4 z_mU}j8U1cq*!>3e?IiH6bBc3dOcvrTIU2-Gmwy&J0$NC3U7VHREg8+~@)h@@lr#b> zwV~NOG6rNWp`NakR$??$br*cTwNe0X%tpZ4ya*u4@EJO^>;c>|Ey+E+EM*Unb$pS2 zYF#BrNON;w9>iT0GY=TEsMezu1E4C|-8{#}D+a*x7B~-Zy%GRS?nX9mHyVMqYOd{uPXpSGs8bI4Dz}+CkVzbU?BQU84Y+ofvXhWt7!Sr;Ve2y2rm5{l=pSFw%IG{d&L8onORUl4ofmjEOp%) zw6Dh`gEyj4p}HC-CtPlGcW^bUe@6h)X@A8z%^!;4Jeh0QIs0HZqmU!h+c4(+oev5Z zE;wY)gWbTj)vW$)1E?xNkoEgABAbB;pix9F(;2?No5LB822u$Q1#fTN;{Ec-PNAnK zyYd0hqQ31dg*O2UIgEnMtVPW|eT%yre& z8DK9kcw4(+f%XURBXAaY0Fmpu-zv`Zt8bf~bA-dN9 O0000KmBYy=bNkl6o4rUlpm$7RMNDT*7PUXmL@(JLr9Do z3@`pBl9&iD=!1#+%GDN3Q3)<~31%Z6q*+aH1@lxm2v+U^3o z3u$qeIdhNa+`HGkzyb@)0ykljlexQlXXbo!=FFZmlajJbL4U+lmXviaYcj5}1A5LQ zDInROWZ^OLV(G9d<*CjlsZkW=A1UPo$#|qrhNykhSof)};jvc@p*I46TIvv~Llht} zEF?Y*OT7P3;?M-3>w!eaJ&9d^09uCugCVKDyA~1*1{+9)Z7T+#1{SWTr>A!dA-vof zfrLN48dkEcfPcGM05ci@)9N&_)BxJ zK0*Ng2!Q;+0hrSaSW5Mq9*OUaNqqZHiG{yPOg*DP+zqUFrj>~3&+1qVFf}z5=T40pRo?C@*#c7PSjZJ0mc?9)B>eU1FA1MlFyyfWl)%ED*5S zY$p-|FjxSLgM2##3M-t^AZZ!R0ZD_%^{HoPw>yfgso1uI@M3SO9(Yzx2Se+hxrV{2z*QQpC^u&I+6kZvud?t}hR8 zBhr@h2;&AoK4K04c4!R%<}ANgdbGBi9)M+6lLo+kpr2}F)(M=MH3_ohu?6M>eC{#{z)7GY;$|P zEr->n2qmukt%cG1d!)J(>1}`b9oGMFmwy5Hen4LaU&Ikk^E+8qz=9v6`M@!9Y6|j_ z3Ls-yfP*pP@%jPV(g_U*YlY;%hkusj0~)l2ATEuqZ$hK!7TDexokd;V`U&t~vG6j3 zI#5mx2;AEx@!+NC()q**n6wKH;}w%}5FJ2QKBHFP*Ea!oTeZ(GT?8B)k1i+gBY(gT zuL?ZbC-G=+RQdP&fX|ASy<*aJS`om&z(C=0OGt$vsn>E^H5@LWWyE`X7A5V^(ausq z;X%0@@Kv|O{K#p<&PIXX3+7S+`{Vga;L>4PRKaK=OPN%b{xVV^?kENrd!;-R^5`oAfc}TjftgN)E zs_OO1%F5S_Qc+Q{;JbJwR$CsUlnRfz>e-(UQC?o2L612kx$7i3JZ3yI{~y)))7RTT RwGaRR002ovPDHLkV1oGXid6sr diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 3025631aaea2d1a6b907a6a9d32e2bdc3eccd3a9..dc86f2e2703b185ba9ae9cc4abf756f2e29dda9a 100644 GIT binary patch delta 2907 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 delta 3023 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 OA6c3a&R3ccDgOmy3Yk3s diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 7ad22e200431830950067359fcc466a9dd0568bd..87bb97240364befd4878462b59aac7f1fb288e82 100644 GIT binary patch literal 4873 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 U?-bX~rxif^p@Dj(ntjCo0KBL&`~Uy| literal 5164 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 literal 7696 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 P2%r0ETB;SwmJ$C0e(w2n diff --git a/build.gradle b/build.gradle index 63e7f9ed..fbbb51ab 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.0' + ext.kotlin_version = '1.3.21' repositories { google() jcenter() @@ -9,7 +9,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }