diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c67b59fa..3b25121b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -82,6 +82,12 @@ android:label="@string/speed_dial" android:parentActivityName=".activities.SettingsActivity" /> + + diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/activities/CallActivity.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/activities/CallActivity.kt index 9c18bde5..a7e39db9 100644 --- a/app/src/main/kotlin/com/simplemobiletools/dialer/activities/CallActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/activities/CallActivity.kt @@ -18,14 +18,13 @@ import android.view.MotionEvent import android.view.WindowManager import android.widget.ImageView import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.commons.helpers.* +import com.simplemobiletools.commons.helpers.LOWER_ALPHA +import com.simplemobiletools.commons.helpers.MINUTE_SECONDS +import com.simplemobiletools.commons.helpers.isOreoMr1Plus +import com.simplemobiletools.commons.helpers.isOreoPlus import com.simplemobiletools.dialer.R -import com.simplemobiletools.dialer.extensions.addCharacter -import com.simplemobiletools.dialer.extensions.audioManager -import com.simplemobiletools.dialer.extensions.config -import com.simplemobiletools.dialer.extensions.getHandleToUse -import com.simplemobiletools.dialer.helpers.CallContactAvatarHelper -import com.simplemobiletools.dialer.helpers.CallManager +import com.simplemobiletools.dialer.extensions.* +import com.simplemobiletools.dialer.helpers.* import com.simplemobiletools.dialer.models.CallContact import kotlinx.android.synthetic.main.activity_call.* import kotlinx.android.synthetic.main.dialpad.* @@ -34,7 +33,7 @@ class CallActivity : SimpleActivity() { companion object { fun getStartIntent(context: Context): Intent { val openAppIntent = Intent(context, CallActivity::class.java) - openAppIntent.flags = Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT or Intent.FLAG_ACTIVITY_NEW_TASK + openAppIntent.flags = Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT return openAppIntent } } @@ -55,29 +54,36 @@ class CallActivity : SimpleActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_call) + if (CallManager.getPhoneState() == NoCall) { + finish() + return + } + updateTextColors(call_holder) initButtons() audioManager.mode = AudioManager.MODE_IN_CALL - CallManager.getCallContact(applicationContext) { contact -> - callContact = contact - val avatar = callContactAvatarHelper.getCallContactAvatar(contact) - runOnUiThread { - updateOtherPersonsInfo(avatar) - checkCalledSIMCard() - } - } - addLockScreenFlags() - CallManager.registerCallback(callCallback) - updateCallState(CallManager.getState()) + CallManager.addListener(callCallback) + + updateCallContactInfo(CallManager.getPrimaryCall()) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + updateState() + } + + override fun onResume() { + super.onResume() + updateState() } override fun onDestroy() { super.onDestroy() - CallManager.unregisterCallback(callCallback) + CallManager.removeListener(callCallback) disableProximitySensor() } @@ -133,6 +139,25 @@ class CallActivity : SimpleActivity() { toggleHold() } + call_add.setOnClickListener { + Intent(applicationContext, DialpadActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) + startActivity(this) + } + } + + call_swap.setOnClickListener { + CallManager.swap() + } + + call_merge.setOnClickListener { + CallManager.merge() + } + + call_manage.setOnClickListener { + startActivity(Intent(this, ConferenceActivity::class.java)) + } + call_end.setOnClickListener { endCall() } @@ -153,7 +178,10 @@ class CallActivity : SimpleActivity() { dialpad_hashtag_holder.setOnClickListener { dialpadPressed('#') } dialpad_wrapper.setBackgroundColor(getProperBackgroundColor()) - arrayOf(call_toggle_microphone, call_toggle_speaker, call_dialpad, dialpad_close, call_sim_image).forEach { + arrayOf( + call_toggle_microphone, call_toggle_speaker, call_dialpad, dialpad_close, + call_sim_image, call_toggle_hold, call_add, call_swap, call_merge, call_manage + ).forEach { it.applyColorFilter(getProperTextColor()) } @@ -334,6 +362,16 @@ class CallActivity : SimpleActivity() { if (avatar != null) { caller_avatar.setImageBitmap(avatar) + } else { + caller_avatar.setImageDrawable(null) + } + } + + private fun getContactNameOrNumber(contact: CallContact): String { + return contact.name.ifEmpty { + contact.number.ifEmpty { + getString(R.string.unknown_caller) + } } } @@ -343,7 +381,7 @@ class CallActivity : SimpleActivity() { val accounts = telecomManager.callCapablePhoneAccounts if (accounts.size > 1) { accounts.forEachIndexed { index, account -> - if (account == CallManager.call?.details?.accountHandle) { + if (account == CallManager.getPrimaryCall()?.details?.accountHandle) { call_sim_id.text = "${index + 1}" call_sim_id.beVisible() call_sim_image.beVisible() @@ -365,7 +403,8 @@ class CallActivity : SimpleActivity() { } } - private fun updateCallState(state: Int) { + private fun updateCallState(call: Call) { + val state = call.getStateCompat() when (state) { Call.STATE_RINGING -> callRinging() Call.STATE_ACTIVE -> callStarted() @@ -376,7 +415,7 @@ class CallActivity : SimpleActivity() { val statusTextId = when (state) { Call.STATE_RINGING -> R.string.is_calling - Call.STATE_DIALING -> R.string.dialing + Call.STATE_CONNECTING, Call.STATE_DIALING -> R.string.dialing else -> 0 } @@ -384,9 +423,53 @@ class CallActivity : SimpleActivity() { call_status_label.text = getString(statusTextId) } - val isActiveCall = state == Call.STATE_ACTIVE || state == Call.STATE_HOLDING - call_toggle_hold.isEnabled = isActiveCall - call_toggle_hold.alpha = if (isActiveCall) 1.0f else LOWER_ALPHA + call_manage.beVisibleIf(call.hasCapability(Call.Details.CAPABILITY_MANAGE_CONFERENCE)) + setActionButtonEnabled(call_swap, state == Call.STATE_ACTIVE) + setActionButtonEnabled(call_merge, state == Call.STATE_ACTIVE) + } + + private fun updateState() { + val phoneState = CallManager.getPhoneState() + if (phoneState is SingleCall) { + updateCallState(phoneState.call) + updateCallOnHoldState(null) + val state = phoneState.call.getStateCompat() + val isSingleCallActionsEnabled = (state == Call.STATE_ACTIVE || state == Call.STATE_DISCONNECTED + || state == Call.STATE_DISCONNECTING || state == Call.STATE_HOLDING) + setActionButtonEnabled(call_toggle_hold, isSingleCallActionsEnabled) + setActionButtonEnabled(call_add, isSingleCallActionsEnabled) + } else if (phoneState is TwoCalls) { + updateCallState(phoneState.active) + updateCallOnHoldState(phoneState.onHold) + } + } + + private fun updateCallOnHoldState(call: Call?) { + val hasCallOnHold = call != null + if (hasCallOnHold) { + getCallContact(applicationContext, call) { contact -> + runOnUiThread { + on_hold_caller_name.text = getContactNameOrNumber(contact) + } + } + } + on_hold_status_holder.beVisibleIf(hasCallOnHold) + controls_single_call.beVisibleIf(!hasCallOnHold) + controls_two_calls.beVisibleIf(hasCallOnHold) + } + + private fun updateCallContactInfo(call: Call?) { + getCallContact(applicationContext, call) { contact -> + if (call != CallManager.getPrimaryCall()) { + return@getCallContact + } + callContact = contact + val avatar = if (!call.isConference()) callContactAvatarHelper.getCallContactAvatar(contact) else null + runOnUiThread { + updateOtherPersonsInfo(avatar) + checkCalledSIMCard() + } + } } private fun acceptCall() { @@ -407,13 +490,14 @@ class CallActivity : SimpleActivity() { enableProximitySensor() incoming_call_holder.beGone() ongoing_call_holder.beVisible() + callDurationHandler.removeCallbacks(updateCallDurationTask) callDurationHandler.post(updateCallDurationTask) } private fun showPhoneAccountPicker() { if (callContact != null) { getHandleToUse(intent, callContact!!.number) { handle -> - CallManager.call?.phoneAccountSelected(handle, false) + CallManager.getPrimaryCall()?.phoneAccountSelected(handle, false) } } } @@ -446,16 +530,21 @@ class CallActivity : SimpleActivity() { } } - private val callCallback = object : Call.Callback() { - override fun onStateChanged(call: Call, state: Int) { - super.onStateChanged(call, state) - updateCallState(state) + private val callCallback = object : CallManagerListener { + override fun onStateChanged() { + updateState() + } + + override fun onPrimaryCallChanged(call: Call) { + callDurationHandler.removeCallbacks(updateCallDurationTask) + updateCallContactInfo(call) + updateState() } } private val updateCallDurationTask = object : Runnable { override fun run() { - callDuration = CallManager.getCallDuration() + callDuration = CallManager.getPrimaryCall().getCallDuration() if (!isCallEnded) { call_status_label.text = callDuration.getFormattedDuration() callDurationHandler.postDelayed(this, 1000) @@ -497,4 +586,11 @@ class CallActivity : SimpleActivity() { proximityWakeLock!!.release() } } + + private fun setActionButtonEnabled(button: ImageView, enabled: Boolean) { + button.apply { + isEnabled = enabled + alpha = if (enabled) 1.0f else LOWER_ALPHA + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/activities/ConferenceActivity.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/activities/ConferenceActivity.kt new file mode 100644 index 00000000..f3633b23 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/activities/ConferenceActivity.kt @@ -0,0 +1,17 @@ +package com.simplemobiletools.dialer.activities + +import android.os.Bundle +import com.simplemobiletools.dialer.R +import com.simplemobiletools.dialer.adapters.ConferenceCallsAdapter +import com.simplemobiletools.dialer.helpers.CallManager +import kotlinx.android.synthetic.main.activity_conference.* + +class ConferenceActivity : SimpleActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_conference) + + conference_calls_list.adapter = ConferenceCallsAdapter(this, conference_calls_list, ArrayList(CallManager.getConferenceCalls())) {} + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/adapters/ConferenceCallsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/adapters/ConferenceCallsAdapter.kt new file mode 100644 index 00000000..961eda02 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/adapters/ConferenceCallsAdapter.kt @@ -0,0 +1,89 @@ +package com.simplemobiletools.dialer.adapters + +import android.telecom.Call +import android.view.Menu +import android.view.ViewGroup +import com.bumptech.glide.Glide +import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter +import com.simplemobiletools.commons.helpers.LOWER_ALPHA +import com.simplemobiletools.commons.helpers.SimpleContactsHelper +import com.simplemobiletools.commons.views.MyRecyclerView +import com.simplemobiletools.dialer.R +import com.simplemobiletools.dialer.activities.SimpleActivity +import com.simplemobiletools.dialer.extensions.hasCapability +import com.simplemobiletools.dialer.helpers.getCallContact +import kotlinx.android.synthetic.main.item_conference_call.view.* + +class ConferenceCallsAdapter( + activity: SimpleActivity, recyclerView: MyRecyclerView, val data: ArrayList, itemClick: (Any) -> Unit +) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) { + + override fun actionItemPressed(id: Int) {} + + override fun getActionMenuId(): Int = 0 + + override fun getIsItemSelectable(position: Int): Boolean = false + + override fun getItemCount(): Int = data.size + + override fun getItemKeyPosition(key: Int): Int = -1 + + override fun getItemSelectionKey(position: Int): Int? = null + + override fun getSelectableItemCount(): Int = data.size + + override fun onActionModeCreated() {} + + override fun onActionModeDestroyed() {} + + override fun prepareActionMode(menu: Menu) {} + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_conference_call, parent) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val call = data[position] + holder.bindView(call, allowSingleClick = false, allowLongClick = false) { itemView, _ -> + getCallContact(itemView.context, call) { callContact -> + itemView.post { + itemView.item_conference_call_name.text = callContact.name.ifEmpty { itemView.context.getString(R.string.unknown_caller) } + SimpleContactsHelper(activity).loadContactImage( + callContact.photoUri, + itemView.item_conference_call_image, + callContact.name, + activity.getDrawable(R.drawable.ic_person_vector) + ) + } + } + val canSeparate = call.hasCapability(Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE) + val canDisconnect = call.hasCapability(Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE) + itemView.item_conference_call_split.isEnabled = canSeparate + itemView.item_conference_call_split.alpha = if (canSeparate) 1.0f else LOWER_ALPHA + itemView.item_conference_call_split.setOnClickListener { + call.splitFromConference() + data.removeAt(position) + notifyItemRemoved(position) + if (data.size == 1) { + activity.finish() + } + } + itemView.item_conference_call_end.isEnabled = canDisconnect + itemView.item_conference_call_end.alpha = if (canDisconnect) 1.0f else LOWER_ALPHA + itemView.item_conference_call_end.setOnClickListener { + call.disconnect() + data.removeAt(position) + notifyItemRemoved(position) + if (data.size == 1) { + activity.finish() + } + } + } + bindViewHolder(holder) + } + + override fun onViewRecycled(holder: ViewHolder) { + super.onViewRecycled(holder) + if (!activity.isDestroyed && !activity.isFinishing) { + Glide.with(activity).clear(holder.itemView.item_conference_call_image) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/extensions/Call.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/extensions/Call.kt index edca4412..80f1173f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/dialer/extensions/Call.kt +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/extensions/Call.kt @@ -9,14 +9,32 @@ import com.simplemobiletools.commons.helpers.isSPlus private val OUTGOING_CALL_STATES = arrayOf(STATE_CONNECTING, STATE_DIALING, STATE_SELECT_PHONE_ACCOUNT) @Suppress("DEPRECATION") -fun Call.getStateCompat(): Int { - return if (isSPlus()) { +fun Call?.getStateCompat(): Int { + return if (this == null) { + Call.STATE_DISCONNECTED + } else if (isSPlus()) { details.state } else { state } } +fun Call?.getCallDuration(): Int { + return if (this != null) { + val connectTimeMillis = details.connectTimeMillis + if (connectTimeMillis == 0L) { + return 0 + } + ((System.currentTimeMillis() - connectTimeMillis) / 1000).toInt() + } else { + 0 + } +} + fun Call.isOutgoing(): Boolean { return OUTGOING_CALL_STATES.contains(getStateCompat()) } + +fun Call.hasCapability(capability: Int): Boolean = (details.callCapabilities and capability) != 0 + +fun Call?.isConference(): Boolean = this?.details?.hasProperty(Call.Details.PROPERTY_CONFERENCE) == true diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallContactHelper.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallContactHelper.kt new file mode 100644 index 00000000..9d0b4cb5 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallContactHelper.kt @@ -0,0 +1,72 @@ +package com.simplemobiletools.dialer.helpers + +import android.content.Context +import android.net.Uri +import android.telecom.Call +import com.simplemobiletools.commons.extensions.getMyContactsCursor +import com.simplemobiletools.commons.extensions.getPhoneNumberTypeText +import com.simplemobiletools.commons.helpers.MyContactsContentProvider +import com.simplemobiletools.commons.helpers.SimpleContactsHelper +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.dialer.R +import com.simplemobiletools.dialer.extensions.isConference +import com.simplemobiletools.dialer.models.CallContact + +fun getCallContact(context: Context, call: Call?, callback: (CallContact) -> Unit) { + if (call.isConference()) { + callback(CallContact(context.getString(R.string.conference), "", "", "")) + return + } + + val privateCursor = context.getMyContactsCursor(false, true) + ensureBackgroundThread { + val callContact = CallContact("", "", "", "") + val handle = try { + call?.details?.handle?.toString() + } catch (e: NullPointerException) { + null + } + + if (handle == null) { + callback(callContact) + return@ensureBackgroundThread + } + + val uri = Uri.decode(handle) + if (uri.startsWith("tel:")) { + val number = uri.substringAfter("tel:") + SimpleContactsHelper(context).getAvailableContacts(false) { contacts -> + val privateContacts = MyContactsContentProvider.getSimpleContacts(context, privateCursor) + if (privateContacts.isNotEmpty()) { + contacts.addAll(privateContacts) + } + + val contactsWithMultipleNumbers = contacts.filter { it.phoneNumbers.size > 1 } + val numbersToContactIDMap = HashMap() + contactsWithMultipleNumbers.forEach { contact -> + contact.phoneNumbers.forEach { phoneNumber -> + numbersToContactIDMap[phoneNumber.value] = contact.contactId + numbersToContactIDMap[phoneNumber.normalizedNumber] = contact.contactId + } + } + + callContact.number = number + val contact = contacts.firstOrNull { it.doesHavePhoneNumber(number) } + if (contact != null) { + callContact.name = contact.name + callContact.photoUri = contact.photoUri + + if (contact.phoneNumbers.size > 1) { + val specificPhoneNumber = contact.phoneNumbers.firstOrNull { it.value == number } + if (specificPhoneNumber != null) { + callContact.numberLabel = context.getPhoneNumberTypeText(specificPhoneNumber.type, specificPhoneNumber.label) + } + } + } else { + callContact.name = number + } + callback(callContact) + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallManager.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallManager.kt index f51ca7fb..57b6239c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallManager.kt +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallManager.kt @@ -1,23 +1,120 @@ package com.simplemobiletools.dialer.helpers -import android.content.Context -import android.net.Uri +import android.annotation.SuppressLint import android.telecom.Call import android.telecom.InCallService import android.telecom.VideoProfile -import com.simplemobiletools.commons.extensions.getMyContactsCursor -import com.simplemobiletools.commons.extensions.getPhoneNumberTypeText -import com.simplemobiletools.commons.helpers.MyContactsContentProvider -import com.simplemobiletools.commons.helpers.SimpleContactsHelper -import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.dialer.extensions.getStateCompat -import com.simplemobiletools.dialer.models.CallContact +import com.simplemobiletools.dialer.extensions.hasCapability +import com.simplemobiletools.dialer.extensions.isConference +import java.util.concurrent.CopyOnWriteArraySet // inspired by https://github.com/Chooloo/call_manage class CallManager { companion object { - var call: Call? = null + @SuppressLint("StaticFieldLeak") var inCallService: InCallService? = null + private var call: Call? = null + private val calls = mutableListOf() + private val listeners = CopyOnWriteArraySet() + + fun onCallAdded(call: Call) { + this.call = call + calls.add(call) + for (listener in listeners) { + listener.onPrimaryCallChanged(call) + } + call.registerCallback(object : Call.Callback() { + override fun onStateChanged(call: Call, state: Int) { + updateState() + } + + override fun onDetailsChanged(call: Call, details: Call.Details) { + updateState() + } + + override fun onConferenceableCallsChanged(call: Call, conferenceableCalls: MutableList) { + updateState() + } + }) + } + + fun onCallRemoved(call: Call) { + calls.remove(call) + updateState() + } + + fun getPhoneState(): PhoneState { + return when (calls.size) { + 0 -> NoCall + 1 -> SingleCall(calls.first()) + 2 -> { + val active = calls.find { it.getStateCompat() == Call.STATE_ACTIVE } + val newCall = calls.find { it.getStateCompat() == Call.STATE_CONNECTING || it.getStateCompat() == Call.STATE_DIALING } + val onHold = calls.find { it.getStateCompat() == Call.STATE_HOLDING } + if (active != null && newCall != null) { + TwoCalls(newCall, active) + } else if (newCall != null && onHold != null) { + TwoCalls(newCall, onHold) + } else if (active != null && onHold != null) { + TwoCalls(active, onHold) + } else { + TwoCalls(calls[0], calls[1]) + } + } + else -> { + val conference = calls.find { it.isConference() }!! + val secondCall = if (conference.children.size + 1 != calls.size) { + calls.filter { !it.isConference() } + .subtract(conference.children.toSet()) + .firstOrNull() + } else { + null + } + if (secondCall == null) { + SingleCall(conference) + } else { + val newCallState = secondCall.getStateCompat() + if (newCallState == Call.STATE_ACTIVE || newCallState == Call.STATE_CONNECTING || newCallState == Call.STATE_DIALING) { + TwoCalls(secondCall, conference) + } else { + TwoCalls(conference, secondCall) + } + } + } + } + } + + private fun updateState() { + val primaryCall = when (val phoneState = getPhoneState()) { + is NoCall -> null + is SingleCall -> phoneState.call + is TwoCalls -> phoneState.active + } + var notify = true + if (primaryCall == null) { + call = null + } else if (primaryCall != call) { + call = primaryCall + for (listener in listeners) { + listener.onPrimaryCallChanged(primaryCall) + } + notify = false + } + if (notify) { + for (listener in listeners) { + listener.onStateChanged() + } + } + } + + fun getPrimaryCall(): Call? { + return call + } + + fun getConferenceCalls(): List { + return calls.find { it.isConference() }?.children ?: emptyList() + } fun accept() { call?.answer(VideoProfile.STATE_AUDIO_ONLY) @@ -43,85 +140,46 @@ class CallManager { return !isOnHold } - fun registerCallback(callback: Call.Callback) { - call?.registerCallback(callback) + fun swap() { + if (calls.size > 1) { + calls.find { it.getStateCompat() == Call.STATE_HOLDING }?.unhold() + } } - fun unregisterCallback(callback: Call.Callback) { - call?.unregisterCallback(callback) + fun merge() { + val conferenceableCalls = call!!.conferenceableCalls + if (conferenceableCalls.isNotEmpty()) { + call!!.conference(conferenceableCalls.first()) + } else { + if (call!!.hasCapability(Call.Details.CAPABILITY_MERGE_CONFERENCE)) { + call!!.mergeConference() + } + } } - fun getState() = if (call == null) { - Call.STATE_DISCONNECTED - } else { - call!!.getStateCompat() + fun addListener(listener: CallManagerListener) { + listeners.add(listener) } + fun removeListener(listener: CallManagerListener) { + listeners.remove(listener) + } + + fun getState() = getPrimaryCall()?.getStateCompat() + fun keypad(c: Char) { call?.playDtmfTone(c) call?.stopDtmfTone() } - - fun getCallContact(context: Context, callback: (CallContact?) -> Unit) { - val privateCursor = context.getMyContactsCursor(false, true) - ensureBackgroundThread { - val callContact = CallContact("", "", "", "") - val handle = try { - call?.details?.handle?.toString() - } catch (e: NullPointerException) { - null - } - - if (handle == null) { - callback(callContact) - return@ensureBackgroundThread - } - - val uri = Uri.decode(handle) - if (uri.startsWith("tel:")) { - val number = uri.substringAfter("tel:") - SimpleContactsHelper(context).getAvailableContacts(false) { contacts -> - val privateContacts = MyContactsContentProvider.getSimpleContacts(context, privateCursor) - if (privateContacts.isNotEmpty()) { - contacts.addAll(privateContacts) - } - - val contactsWithMultipleNumbers = contacts.filter { it.phoneNumbers.size > 1 } - val numbersToContactIDMap = HashMap() - contactsWithMultipleNumbers.forEach { contact -> - contact.phoneNumbers.forEach { phoneNumber -> - numbersToContactIDMap[phoneNumber.value] = contact.contactId - numbersToContactIDMap[phoneNumber.normalizedNumber] = contact.contactId - } - } - - callContact.number = number - val contact = contacts.firstOrNull { it.doesHavePhoneNumber(number) } - if (contact != null) { - callContact.name = contact.name - callContact.photoUri = contact.photoUri - - if (contact.phoneNumbers.size > 1) { - val specificPhoneNumber = contact.phoneNumbers.firstOrNull { it.value == number } - if (specificPhoneNumber != null) { - callContact.numberLabel = context.getPhoneNumberTypeText(specificPhoneNumber.type, specificPhoneNumber.label) - } - } - } else { - callContact.name = number - } - callback(callContact) - } - } - } - } - - fun getCallDuration(): Int { - return if (call != null) { - ((System.currentTimeMillis() - call!!.details.connectTimeMillis) / 1000).toInt() - } else { - 0 - } - } } } + +interface CallManagerListener { + fun onStateChanged() + fun onPrimaryCallChanged(call: Call) +} + +sealed class PhoneState +object NoCall : PhoneState() +class SingleCall(val call: Call) : PhoneState() +class TwoCalls(val active: Call, val onHold: Call) : PhoneState() diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallNotificationManager.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallNotificationManager.kt index 324123b4..ecd286c6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallNotificationManager.kt +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallNotificationManager.kt @@ -28,7 +28,7 @@ class CallNotificationManager(private val context: Context) { @SuppressLint("NewApi") fun setupNotification() { - CallManager.getCallContact(context.applicationContext) { callContact -> + getCallContact(context.applicationContext, CallManager.getPrimaryCall()) { callContact -> val callContactAvatar = callContactAvatarHelper.getCallContactAvatar(callContact) val callState = CallManager.getState() val isHighPriority = context.powerManager.isInteractive && callState == Call.STATE_RINGING diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/services/CallService.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/services/CallService.kt index f1fd2277..43315238 100644 --- a/app/src/main/kotlin/com/simplemobiletools/dialer/services/CallService.kt +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/services/CallService.kt @@ -10,6 +10,7 @@ import com.simplemobiletools.dialer.extensions.isOutgoing import com.simplemobiletools.dialer.extensions.powerManager import com.simplemobiletools.dialer.helpers.CallManager import com.simplemobiletools.dialer.helpers.CallNotificationManager +import com.simplemobiletools.dialer.helpers.NoCall class CallService : InCallService() { private val callNotificationManager by lazy { CallNotificationManager(this) } @@ -25,9 +26,9 @@ class CallService : InCallService() { override fun onCallAdded(call: Call) { super.onCallAdded(call) - CallManager.call = call + CallManager.onCallAdded(call) CallManager.inCallService = this - CallManager.registerCallback(callListener) + call.registerCallback(callListener) val isScreenLocked = (getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager).isDeviceLocked if (!powerManager.isInteractive || call.isOutgoing() || isScreenLocked) { @@ -42,14 +43,22 @@ class CallService : InCallService() { override fun onCallRemoved(call: Call) { super.onCallRemoved(call) - CallManager.call = null - CallManager.inCallService = null - callNotificationManager.cancelNotification() + call.unregisterCallback(callListener) + val wasPrimaryCall = call == CallManager.getPrimaryCall() + CallManager.onCallRemoved(call) + if (CallManager.getPhoneState() == NoCall) { + CallManager.inCallService = null + callNotificationManager.cancelNotification() + } else { + callNotificationManager.setupNotification() + if (wasPrimaryCall) { + startActivity(CallActivity.getStartIntent(this)) + } + } } override fun onDestroy() { super.onDestroy() - CallManager.unregisterCallback(callListener) callNotificationManager.cancelNotification() } } diff --git a/app/src/main/res/drawable/ic_add_call_vector.xml b/app/src/main/res/drawable/ic_add_call_vector.xml new file mode 100644 index 00000000..059b7e05 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_call_vector.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_call_merge_vector.xml b/app/src/main/res/drawable/ic_call_merge_vector.xml new file mode 100644 index 00000000..280463cd --- /dev/null +++ b/app/src/main/res/drawable/ic_call_merge_vector.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_call_split_vector.xml b/app/src/main/res/drawable/ic_call_split_vector.xml new file mode 100644 index 00000000..5afba98d --- /dev/null +++ b/app/src/main/res/drawable/ic_call_split_vector.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_call_swap_vector.xml b/app/src/main/res/drawable/ic_call_swap_vector.xml new file mode 100644 index 00000000..35ec9c98 --- /dev/null +++ b/app/src/main/res/drawable/ic_call_swap_vector.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_call.xml b/app/src/main/res/layout/activity_call.xml index e846a9d7..bc436dcb 100644 --- a/app/src/main/res/layout/activity_call.xml +++ b/app/src/main/res/layout/activity_call.xml @@ -102,6 +102,55 @@ app:layout_constraintTop_toTopOf="@+id/call_sim_image" tools:text="1" /> + + + + + + + + + + @@ -131,8 +179,8 @@ android:padding="@dimen/medium_margin" android:src="@drawable/ic_speaker_off_vector" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@+id/call_dialpad" + app:layout_constraintStart_toEndOf="@+id/call_toggle_microphone" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.6" /> @@ -146,8 +194,7 @@ android:src="@drawable/ic_dialpad_vector" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.85" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintStart_toEndOf="@+id/call_toggle_speaker" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.6" /> @@ -157,11 +204,67 @@ android:layout_height="@dimen/dialpad_button_size" android:layout_marginTop="@dimen/bigger_margin" android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/hold_call" android:padding="@dimen/medium_margin" android:src="@drawable/ic_pause_vector" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toEndOf="@+id/call_toggle_microphone" + app:layout_constraintStart_toStartOf="@+id/call_toggle_microphone" + app:layout_constraintTop_toBottomOf="@+id/call_toggle_microphone" /> + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_conference_call.xml b/app/src/main/res/layout/item_conference_call.xml new file mode 100644 index 00000000..0833746b --- /dev/null +++ b/app/src/main/res/layout/item_conference_call.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 362e3cbf..30a463ab 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Бързо набиране Управление на бързото набиране diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 45824070..789e6db9 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -47,6 +47,12 @@ Trucada en espera Reprèn la trucada En espera + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Marcatge ràpid Gestiona el marcatge ràpid @@ -66,4 +72,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 10e75b2d..726b18f0 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Rychlé vytáčení Spravujte rychlou volbu @@ -89,4 +95,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 3e872a25..9a8ea6e7 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Hurtigopkald Administrér hurtigopkald diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 68d68a5a..aafddd1c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -47,6 +47,12 @@ Anruf halten Anruf fortsetzen In der Warteschleife + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Kurzwahl Kurzwahlnummern verwalten diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 3460f71a..83623ff3 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -50,6 +50,12 @@ Αναμονή Κλήσης Συνέχιση Κλήσης Σε Αναμονή + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Ταχεία κλήση diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 1cb048f9..55f8791f 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -51,6 +51,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Speed dial diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 034edd79..9e6776a9 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Marcado rápido Administrar marcado rápido diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 03156089..9db88df1 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Kiirvalimine Klõpsa numbrit ja seosta ta konkreetse telefoniraamatu kirjega. Hiljem saad samale numbrile pikalt vajutades alustada kõnet määratud telefoninumbrile. diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 4b282eae..9c435086 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Pikavalinta Pikavalinnan asetukset diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index ada667b7..d312d1b0 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,6 +1,6 @@ - Simple Dialer + Simple Téléphone Téléphone Veuillez faire de cette application votre application de téléphone par défaut Veuillez permettre l\'affichage sur d\'autres applications pour un comportement fiable @@ -47,6 +47,12 @@ Mettre l\'appel en attente Reprendre l\'appel En attente + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Numérotation rapide Gérer la numérotation rapide @@ -89,4 +95,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 7ad2eb8d..7992fee5 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Marcación rápida Xestionar marcacións rápidas diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index ea8d7596..acdaf48f 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Brzo biranje Upravljajte brzim biranjem diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 7dce2a65..dcd524d6 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Gyors tárcsázó Gyors tárcsázó kezelése diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 12a84dcd..1cf3774a 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Panggilan cepat Kelola panggilan cepat diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 5a2c2688..c827ccc9 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -47,6 +47,12 @@ Metti in attesa la chiamata Riprendi la chiamata In attesa + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Contatti veloci Gestisci i contatti veloci diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index e80a3d90..f250b88f 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference スピードダイヤル スピードダイヤルの管理 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 3965a88d..cadc4e52 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Spartusis rinkimas Tvarkyti spartųjį rinkimą diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 421bb22f..75e69c6d 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -50,6 +50,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference സ്പീഡ് ഡയൽ diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index bc8d131c..f9da0b13 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Hurtigvalg Administrer hurtigvalg diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 566bca95..ea80b104 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -47,6 +47,12 @@ In wacht Hervatten In de wacht + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Snelkiesnummer Snelkiezen beheren @@ -67,4 +73,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 53342a16..ce1bd325 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -47,6 +47,12 @@ Wstrzymaj połączenie Wznów połączenie Wstrzymane + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Szybkie wybieranie Zarządzaj szybkim wybieraniem @@ -89,4 +95,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 1021b603..d3068e16 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Ligação rápida Gerenciar ligações rápidas diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index e0f70658..84aff255 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Marcação rápida Gerir marcações rápidas diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 26191471..254b0a46 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Apelare rapidă Gestionați apelarea rapidă diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4f5dd650..1260d75f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Быстрый вызов Управление быстрым вызовом diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index bd336bc1..94f67e92 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -51,6 +51,12 @@ Podržať hovor Obnoviť hovor Podržaný hovor + Prehodiť hovory + Zlúčiť hovory + Rozdeliť call + Pridať hovor + Spravovať konferenčný hovor + Konferencia Rýchle vytáčanie diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 0d1f39bf..e4d05fc8 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -47,6 +47,12 @@ Parkera samtal Återuppta samtal Parkerat + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Snabbuppringning Hantera snabbuppringning diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 962cebba..a790df02 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -47,6 +47,12 @@ Aramayı beklet Aramayı devam ettir Beklemede + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Hızlı arama Hızlı aramayı yönet @@ -66,4 +72,4 @@ Bazı dizeleri bulamadınız mı? Burada daha fazlası var: https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index fb967106..cda650ec 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -46,7 +46,13 @@ Завершити виклик Перевести в утримання Зняти з утримання - Дзвінок утримується + Утримується + Поміняти + З\'єднати + Роз\'єднати + Додати дзвінок + Керувати конференц-викликом + Конференц-виклик Швидкий виклик Управління швидким викликом diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a0626fa4..ed180af7 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -47,6 +47,12 @@ 保持通话 继续通话 暂停 + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference 快速拨号 管理快速拨号 @@ -89,4 +95,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6aadb180..4236c343 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -47,6 +47,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Speed dial diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 928b9370..c32cc0e8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -51,6 +51,12 @@ Hold call Resume call On Hold + Swap calls + Merge calls + Split call + Add call + Manage conference call + Conference Speed dial