diff --git a/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/InAppChat.java b/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/InAppChat.java index 697d7e81..a4830576 100644 --- a/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/InAppChat.java +++ b/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/InAppChat.java @@ -191,4 +191,11 @@ public synchronized static InAppChat getInstance(Context context) { * @param theme data object holding all style attributes */ public abstract void setTheme(InAppChatTheme theme); + + /** + * Get current theme. Theme is alternative to defining style in xml. + * + * @return theme data object holding all style attributes + */ + public abstract InAppChatTheme getTheme(); } diff --git a/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/InAppChatImpl.java b/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/InAppChatImpl.java index 0a23af97..82241cc4 100644 --- a/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/InAppChatImpl.java +++ b/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/InAppChatImpl.java @@ -299,6 +299,7 @@ public void setTheme(InAppChatTheme theme) { this.theme = theme; } + @Override @Nullable public InAppChatTheme getTheme() { return theme; diff --git a/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/InAppChatFragment.kt b/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/InAppChatFragment.kt index df36f13f..024f5ea1 100644 --- a/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/InAppChatFragment.kt +++ b/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/InAppChatFragment.kt @@ -217,7 +217,7 @@ class InAppChatFragment : Fragment(), InAppChatFragmentActivityResultDelegate.Re } fun sendContextualMetaData(data: String, allMultiThreadStrategy: Boolean) { - binding.ibLcChat.sendContextualMetaData(data, allMultiThreadStrategy) + binding.ibLcChat.sendContextualData(data, allMultiThreadStrategy) } //endregion diff --git a/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/InAppChatView.kt b/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/InAppChatView.kt index ddb68ef0..5e586451 100644 --- a/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/InAppChatView.kt +++ b/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/InAppChatView.kt @@ -270,9 +270,19 @@ class InAppChatView @JvmOverloads constructor( * @param data contextual data in the form of JSON string * @param allMultiThreadStrategy multithread strategy flag, true -> ALL, false -> ACTIVE */ + @Deprecated("Use new sendContextualData() instead.", replaceWith = ReplaceWith("sendContextualData(data, allMultiThreadStrategy)"), level = DeprecationLevel.WARNING) fun sendContextualMetaData(data: String, allMultiThreadStrategy: Boolean) { - val flag = - if (allMultiThreadStrategy) InAppChatMultiThreadFlag.ALL else InAppChatMultiThreadFlag.ACTIVE + sendContextualData(data, allMultiThreadStrategy) + } + + /** + * Set contextual data of the Livechat Widget + * + * @param data contextual data in the form of JSON string + * @param allMultiThreadStrategy multithread strategy flag, true -> ALL, false -> ACTIVE + */ + fun sendContextualData(data: String, allMultiThreadStrategy: Boolean) { + val flag = if (allMultiThreadStrategy) InAppChatMultiThreadFlag.ALL else InAppChatMultiThreadFlag.ACTIVE inAppChatClient.sendContextualData(data, flag) } diff --git a/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/styles/factory/StyleFactory.kt b/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/styles/factory/StyleFactory.kt index ca6fdb22..291ad212 100644 --- a/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/styles/factory/StyleFactory.kt +++ b/infobip-mobile-messaging-android-chat-sdk/src/main/java/org/infobip/mobile/messaging/chat/view/styles/factory/StyleFactory.kt @@ -3,7 +3,7 @@ package org.infobip.mobile.messaging.chat.view.styles.factory import android.content.Context import android.util.AttributeSet import org.infobip.mobile.messaging.api.chat.WidgetInfo -import org.infobip.mobile.messaging.chat.InAppChatImpl +import org.infobip.mobile.messaging.chat.InAppChat import org.infobip.mobile.messaging.chat.view.styles.InAppChatInputViewStyle import org.infobip.mobile.messaging.chat.view.styles.InAppChatStyle import org.infobip.mobile.messaging.chat.view.styles.InAppChatToolbarStyle @@ -22,7 +22,7 @@ interface StyleFactory { companion object { fun create(context: Context, attributeSet: AttributeSet? = null, widgetInfo: WidgetInfo? = null): StyleFactory { - return InAppChatImpl.getInstance(context).theme?.let { + return InAppChat.getInstance(context).theme?.let { RuntimeThemeFactory(it) } ?: XMLThemeFactory(context, attributeSet, widgetInfo) } diff --git a/infobip-mobile-messaging-android-demo/src/chat/java/org/infobip/mobile/messaging/demo/MainActivity.java b/infobip-mobile-messaging-android-demo/src/chat/java/org/infobip/mobile/messaging/demo/MainActivity.java index 2be1bfb4..292b09f9 100644 --- a/infobip-mobile-messaging-android-demo/src/chat/java/org/infobip/mobile/messaging/demo/MainActivity.java +++ b/infobip-mobile-messaging-android-demo/src/chat/java/org/infobip/mobile/messaging/demo/MainActivity.java @@ -30,6 +30,7 @@ import com.google.android.material.button.MaterialButtonToggleGroup; import com.google.android.material.progressindicator.LinearProgressIndicator; import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; import org.infobip.mobile.messaging.BroadcastParameter; import org.infobip.mobile.messaging.Event; @@ -37,6 +38,7 @@ import org.infobip.mobile.messaging.SuccessPending; import org.infobip.mobile.messaging.User; import org.infobip.mobile.messaging.chat.InAppChat; +import org.infobip.mobile.messaging.chat.core.InAppChatEvent; import org.infobip.mobile.messaging.chat.utils.DarkModeUtils; import org.infobip.mobile.messaging.chat.view.InAppChatFragment; import org.infobip.mobile.messaging.chat.view.styles.InAppChatDarkMode; @@ -74,6 +76,7 @@ public class MainActivity extends AppCompatActivity implements InAppChatFragment private final String WIDGET_SECRET_KEY_JSON = "your_widget_secret_key"; private final InAppChat inAppChat = InAppChat.getInstance(this); private boolean pushRegIdReceiverRegistered = false; + private boolean lcRegIdReceiverRegistered = false; private JWTSubjectType jwtSubjectType = null; private AuthData lastUsedAuthData = null; private TextInputEditText nameEditText = null; @@ -88,6 +91,15 @@ public void onReceive(Context context, Intent intent) { } } }; + private final BroadcastReceiver lcRegIdReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null) { + String lcRegId = intent.getStringExtra(BroadcastParameter.EXTRA_LIVECHAT_REGISTRATION_ID); + showLivechatRegId(lcRegId); + } + } + }; /* InAppChatActionBarProvider */ @Nullable @@ -111,6 +123,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { setSupportActionBar(this.findViewById(R.id.toolbar)); inAppChat.activate(); setUpPushRegIdField(); + setUpLivechatRegIdField(); setUpSubjectTypeSpinner(); setUpOpenChatActivityButton(); setUpOpenChatFragmentButton(); @@ -124,14 +137,21 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { @Override protected void onDestroy() { - if (this.pushRegIdReceiverRegistered) { + pushRegIdReceiverRegistered = !unregisterBroadcastReceiver(pushRegIdReceiverRegistered, pushRegIdReceiver); + lcRegIdReceiverRegistered = !unregisterBroadcastReceiver(lcRegIdReceiverRegistered, lcRegIdReceiver); + super.onDestroy(); + } + + private boolean unregisterBroadcastReceiver(Boolean isRegistered, BroadcastReceiver receiver) { + if (isRegistered) { try { - LocalBroadcastManager.getInstance(this).unregisterReceiver(pushRegIdReceiver); + LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver); + return true; } catch (Throwable t) { - MobileMessagingLogger.e(TAG, "Unable to unregister pushRegIdReceiverRegistered", t); + MobileMessagingLogger.e("MainActivity", "Unable to unregister broadcast receiver", t); } } - super.onDestroy(); + return false; } @Override @@ -160,8 +180,8 @@ public boolean onCreateOptionsMenu(Menu menu) { @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.action_id) { - setPushRegIdToClipboard(getPushRegId()); - Toast.makeText(this, R.string.toast_registration_id_copy, Toast.LENGTH_SHORT).show(); + saveToClipboard(getString(R.string.push_registration_id), getPushRegId()); + Toast.makeText(this, getString(R.string.push_registration_id) + " " + getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show(); return true; } else if (item.getGroupId() == R.id.languages) { String language = langMenuIdToLocale(item.getItemId()); @@ -252,11 +272,10 @@ private void setUpPushRegIdField() { } } - private void setPushRegIdToClipboard(String pushRegId) { + private void saveToClipboard(String label, String value) { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText(getString(R.string.action_registration_id_copy), pushRegId); + ClipData clip = ClipData.newPlainText(label, value); clipboard.setPrimaryClip(clip); - Toast.makeText(this, R.string.toast_registration_id_copy, Toast.LENGTH_SHORT).show(); } private boolean showPushRegId(String pushRegId) { @@ -265,13 +284,33 @@ private boolean showPushRegId(String pushRegId) { pushRegIdEditText.setText(pushRegId); pushRegIdEditText.setKeyListener(null); pushRegIdEditText.setOnClickListener(view -> { - setPushRegIdToClipboard(pushRegId); + saveToClipboard(getString(R.string.push_registration_id), pushRegId); }); return true; } return false; } + private void setUpLivechatRegIdField() { + TextInputLayout lcRegIdInputLayout = findViewById(R.id.lcRegIdInputLayout); + lcRegIdInputLayout.setVisibility(View.VISIBLE); + if (!lcRegIdReceiverRegistered) { + LocalBroadcastManager.getInstance(this).registerReceiver(lcRegIdReceiver, new IntentFilter(InAppChatEvent.LIVECHAT_REGISTRATION_ID_UPDATED.getKey())); + this.pushRegIdReceiverRegistered = true; + } + } + + private void showLivechatRegId(String lcRegId) { + if (StringUtils.isNotBlank(lcRegId)) { + TextInputEditText lcRegIdEditText = findViewById(R.id.lcRegIdEditText); + lcRegIdEditText.setText(lcRegId); + lcRegIdEditText.setKeyListener(null); + lcRegIdEditText.setOnClickListener(view -> { + saveToClipboard(getString(R.string.livechat_registration_id), lcRegId); + }); + } + } + private void setUpSubjectTypeSpinner() { AutoCompleteTextView autoCompleteTextView = findViewById(R.id.subjectTypeAutocompleteTextView); ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.subject_types, android.R.layout.simple_dropdown_item_1line); diff --git a/infobip-mobile-messaging-android-demo/src/chatCommon/res/layout/activity_main.xml b/infobip-mobile-messaging-android-demo/src/chatCommon/res/layout/activity_main.xml index fce90308..0e89fc66 100644 --- a/infobip-mobile-messaging-android-demo/src/chatCommon/res/layout/activity_main.xml +++ b/infobip-mobile-messaging-android-demo/src/chatCommon/res/layout/activity_main.xml @@ -41,7 +41,7 @@ android:layout_marginLeft="8dp" android:layout_marginTop="8dp" android:layout_marginRight="8dp" - app:layout_constraintBottom_toTopOf="@+id/nameIdInputLayout" + app:layout_constraintBottom_toTopOf="@+id/lcRegIdInputLayout" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -56,6 +56,31 @@ + + + + + + 简体中文 Push registration ID + Livechat registration ID Customer name Subject type @@ -50,8 +51,7 @@ Dark Light Auto - Registration ID copied to clipboard - Registration ID + copied to clipboard Language changed to %1$s Custom button checked Custom button enabled diff --git a/infobip-mobile-messaging-android-demo/src/chatWithCalls/java/org/infobip/mobile/messaging/demo/MainActivity.java b/infobip-mobile-messaging-android-demo/src/chatWithCalls/java/org/infobip/mobile/messaging/demo/MainActivity.java index 8cfc083a..5f2c2c55 100644 --- a/infobip-mobile-messaging-android-demo/src/chatWithCalls/java/org/infobip/mobile/messaging/demo/MainActivity.java +++ b/infobip-mobile-messaging-android-demo/src/chatWithCalls/java/org/infobip/mobile/messaging/demo/MainActivity.java @@ -31,6 +31,7 @@ import com.google.android.material.button.MaterialButtonToggleGroup; import com.google.android.material.progressindicator.LinearProgressIndicator; import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; import com.infobip.webrtc.ui.InfobipRtcUi; import com.infobip.webrtc.ui.model.InCallButton; @@ -40,6 +41,7 @@ import org.infobip.mobile.messaging.SuccessPending; import org.infobip.mobile.messaging.User; import org.infobip.mobile.messaging.chat.InAppChat; +import org.infobip.mobile.messaging.chat.core.InAppChatEvent; import org.infobip.mobile.messaging.chat.utils.DarkModeUtils; import org.infobip.mobile.messaging.chat.view.InAppChatFragment; import org.infobip.mobile.messaging.chat.view.styles.InAppChatDarkMode; @@ -76,6 +78,7 @@ public class MainActivity extends AppCompatActivity implements InAppChatFragment private final String WIDGET_SECRET_KEY_JSON = "your_widget_secret_key"; private final InAppChat inAppChat = InAppChat.getInstance(this); private boolean pushRegIdReceiverRegistered = false; + private boolean lcRegIdReceiverRegistered = false; private JWTSubjectType jwtSubjectType = null; private AuthData lastUsedAuthData = null; private TextInputEditText nameEditText = null; @@ -90,6 +93,15 @@ public void onReceive(Context context, Intent intent) { } } }; + private final BroadcastReceiver lcRegIdReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null) { + String lcRegId = intent.getStringExtra(BroadcastParameter.EXTRA_LIVECHAT_REGISTRATION_ID); + showLivechatRegId(lcRegId); + } + } + }; /* InAppChatActionBarProvider */ @Nullable @@ -100,7 +112,7 @@ public ActionBar getOriginalSupportActionBar() { @Override public void onInAppChatBackPressed() { - InAppChat.getInstance(MainActivity.this).hideInAppChatFragment(getSupportFragmentManager(), true); + inAppChat.hideInAppChatFragment(getSupportFragmentManager(), true); } @Override @@ -113,6 +125,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { setSupportActionBar(this.findViewById(R.id.toolbar)); inAppChat.activate(); setUpPushRegIdField(); + setUpLivechatRegIdField(); setUpSubjectTypeSpinner(); setUpOpenChatActivityButton(); setUpOpenChatFragmentButton(); @@ -127,15 +140,21 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { @Override protected void onDestroy() { - if (this.pushRegIdReceiverRegistered) { + pushRegIdReceiverRegistered = !unregisterBroadcastReceiver(pushRegIdReceiverRegistered, pushRegIdReceiver); + lcRegIdReceiverRegistered = !unregisterBroadcastReceiver(lcRegIdReceiverRegistered, lcRegIdReceiver); + super.onDestroy(); + } + + private boolean unregisterBroadcastReceiver(Boolean isRegistered, BroadcastReceiver receiver) { + if (isRegistered) { try { - LocalBroadcastManager.getInstance(this).unregisterReceiver(pushRegIdReceiver); - pushRegIdReceiverRegistered = false; + LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver); + return true; } catch (Throwable t) { - MobileMessagingLogger.e("MainActivity", "Unable to unregister pushRegIdReceiver", t); + MobileMessagingLogger.e("MainActivity", "Unable to unregister broadcast receiver", t); } } - super.onDestroy(); + return false; } @Override @@ -164,14 +183,14 @@ public boolean onCreateOptionsMenu(Menu menu) { @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.action_id) { - setPushRegIdToClipboard(getPushRegId()); - Toast.makeText(this, R.string.toast_registration_id_copy, Toast.LENGTH_SHORT).show(); + saveToClipboard(getString(R.string.push_registration_id), getPushRegId()); + Toast.makeText(this, getString(R.string.push_registration_id) + " " + getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show(); return true; } else if (item.getGroupId() == R.id.languages) { String language = langMenuIdToLocale(item.getItemId()); //change language of In-app chat and calls if (language != null) { - InAppChat.getInstance(this).setLanguage(language); + inAppChat.setLanguage(language); InfobipRtcUi.getInstance(this).setLanguage(new Locale(language)); Toast.makeText(this, getString(R.string.language_changed, item.getTitle()), Toast.LENGTH_SHORT).show(); } @@ -251,11 +270,10 @@ private void setUpPushRegIdField() { } } - private void setPushRegIdToClipboard(String pushRegId) { + private void saveToClipboard(String label, String value) { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText(getString(R.string.action_registration_id_copy), pushRegId); + ClipData clip = ClipData.newPlainText(label, value); clipboard.setPrimaryClip(clip); - Toast.makeText(this, R.string.toast_registration_id_copy, Toast.LENGTH_SHORT).show(); } private boolean showPushRegId(String pushRegId) { @@ -264,13 +282,33 @@ private boolean showPushRegId(String pushRegId) { pushRegIdEditText.setText(pushRegId); pushRegIdEditText.setKeyListener(null); pushRegIdEditText.setOnClickListener(view -> { - setPushRegIdToClipboard(pushRegId); + saveToClipboard(getString(R.string.push_registration_id), pushRegId); }); return true; } return false; } + private void setUpLivechatRegIdField() { + TextInputLayout lcRegIdInputLayout = findViewById(R.id.lcRegIdInputLayout); + lcRegIdInputLayout.setVisibility(View.VISIBLE); + if (!lcRegIdReceiverRegistered) { + LocalBroadcastManager.getInstance(this).registerReceiver(lcRegIdReceiver, new IntentFilter(InAppChatEvent.LIVECHAT_REGISTRATION_ID_UPDATED.getKey())); + this.pushRegIdReceiverRegistered = true; + } + } + + private void showLivechatRegId(String lcRegId) { + if (StringUtils.isNotBlank(lcRegId)) { + TextInputEditText lcRegIdEditText = findViewById(R.id.lcRegIdEditText); + lcRegIdEditText.setText(lcRegId); + lcRegIdEditText.setKeyListener(null); + lcRegIdEditText.setOnClickListener(view -> { + saveToClipboard(getString(R.string.livechat_registration_id), lcRegId); + }); + } + } + private void setUpSubjectTypeSpinner() { AutoCompleteTextView autoCompleteTextView = findViewById(R.id.subjectTypeAutocompleteTextView); ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.subject_types, android.R.layout.simple_dropdown_item_1line); diff --git a/infobip-rtc-ui/build.gradle b/infobip-rtc-ui/build.gradle index 1365f2b1..7c238f3a 100644 --- a/infobip-rtc-ui/build.gradle +++ b/infobip-rtc-ui/build.gradle @@ -76,7 +76,7 @@ dependencies { implementation "androidx.core:core-ktx:$mm_coreKtxVersion" implementation "androidx.appcompat:appcompat:$mm_appCompatVersion" implementation "com.google.android.material:material:$mm_materialVersion" - api ("com.infobip:infobip-rtc:2.2.11") { + api ("com.infobip:infobip-rtc:2.3.1") { transitive = true } implementation "androidx.work:work-runtime-ktx:2.8.1" diff --git a/infobip-rtc-ui/src/main/AndroidManifest.xml b/infobip-rtc-ui/src/main/AndroidManifest.xml index fbca784a..349545f6 100644 --- a/infobip-rtc-ui/src/main/AndroidManifest.xml +++ b/infobip-rtc-ui/src/main/AndroidManifest.xml @@ -12,10 +12,12 @@ + + - + + + + + - var autoDeclineOnMissingNotificationPermission: Boolean - var rtcUiMode: RtcUiMode? - - fun clear() -} - -internal class InMemoryCache : Cache { - override var configurationId: String = "" - override var identity: String = "" - override var activityClass: Class = CallActivity::class.java - override var autoDeclineOnMissingNotificationPermission: Boolean = true - override var rtcUiMode: RtcUiMode? = null - - override fun clear() { - identity = "" - rtcUiMode = null - } -} \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/InfobipRtcUi.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/InfobipRtcUi.kt index b10eb1dd..d9760815 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/InfobipRtcUi.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/InfobipRtcUi.kt @@ -1,13 +1,15 @@ package com.infobip.webrtc.ui +import android.Manifest import android.app.Activity import android.content.Context import android.util.Log -import com.infobip.webrtc.Injector -import com.infobip.webrtc.TAG +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.core.TAG +import com.infobip.webrtc.ui.internal.model.RtcUiMode import com.infobip.webrtc.ui.model.InCallButton import com.infobip.webrtc.ui.model.ListenType -import com.infobip.webrtc.ui.model.RtcUiMode +import com.infobip.webrtc.ui.model.RtcUiError import com.infobip.webrtc.ui.view.styles.InfobipRtcUiTheme import org.infobip.mobile.messaging.util.ResourceLoader import java.util.Locale @@ -23,19 +25,6 @@ interface InfobipRtcUi { class Builder(private val context: Context) { - /** - * [WebRTC application concept is deprecated.](https://www.infobip.com/docs/voice-and-video/webrtc#webrtc-sdk-1-x-deprecated-) - * You must migrate to new [push configuration approach](https://www.infobip.com/docs/voice-and-video/webrtc#set-up-web-and-in-app-calls) to make calls working. - * This function has no effect anymore. - * - * @return [InfobipRtcUi.Builder] - */ - @Deprecated( - "WebRTC application concept is deprecated. Use push configuration id instead", - ReplaceWith("withConfigurationId()") - ) - fun applicationId(appId: String) = this - /** * Defines Infobip [WebRTC configuration ID](https://www.infobip.com/docs/api/channels/webrtc-calls/webrtc/save-push-configuration) to use. * @@ -63,7 +52,7 @@ interface InfobipRtcUi { } /** - * Set whether incoming call should be declined in case of missing notification permission. Default value is true. + * Set whether incoming InfobipRtcUi call should be declined in case of missing [Manifest.permission.POST_NOTIFICATIONS] permission. Default value is true. * * @param decline true if call is automatically declined, false otherwise * @return [InfobipRtcUi.Builder] @@ -72,6 +61,45 @@ interface InfobipRtcUi { Injector.cache.autoDeclineOnMissingNotificationPermission = decline } + /** + * Set whether incoming InfobipRtcUi call should be declined in case of missing [Manifest.permission.READ_PHONE_STATE] permission. Default value is false. + * + * Default value ensures to not miss first call ever. [Manifest.permission.READ_PHONE_STATE] permission is required only on Android 12 (API 31) + * and higher and it is requested in runtime by InfobipRtcUi library once call UI appears. Your application can request the permission, ensures is granted and set the value to true. + * + * @param decline true if call is automatically declined, false otherwise + * @return [InfobipRtcUi.Builder] + */ + fun withAutoDeclineOnMissingReadPhoneStatePermission(decline: Boolean) = apply { + Injector.cache.autoDeclineOnMissingReadPhoneStatePermission = decline + } + + /** + * Set whether incoming InfobipRtcUi call should be declined when there is ringing or ongoing cellular call. Default value is true. + * + * Default value ensures to not miss first call ever. [Manifest.permission.READ_PHONE_STATE] permission is required only on Android 12 (API 31) + * and higher and it is requested in runtime by InfobipRtcUi library once call UI appears. Your application can request the permission, ensures is granted and set the value to true. + * + * @param decline true if call is automatically declined, false otherwise + * @return [InfobipRtcUi.Builder] + */ + fun withAutoDeclineWhenOngoingCellularCall(decline: Boolean) = apply { + Injector.cache.autoDeclineWhenOngoingCellularCall = decline + } + + /** + * Set whether ongoing InfobipRtcUi call should be finished when incoming cellular call is accepted. Default value is true. + * + * On Android 12 (API 31) and higher, the setting is ignored if [Manifest.permission.READ_PHONE_STATE] permission is not granted. + * [Manifest.permission.READ_PHONE_STATE] permission is requested in runtime by InfobipRtcUi library once call UI appears. + * + * @param finish true if call is automatically declined, false otherwise + * @return [InfobipRtcUi.Builder] + */ + fun withAutoFinishWhenIncomingCellularCallAccepted(finish: Boolean) = apply { + Injector.cache.autoFinishWhenIncomingCellularCallAccepted = finish + } + /** * Enables incoming calls for InAppChat. Calls are not enabled immediately, it is waiting for InAppChat to provide livechatRegistrationId what is used as identity, listenType is PUSH. * If successListener is not provided, default null is used. @@ -162,10 +190,8 @@ interface InfobipRtcUi { override fun build(): InfobipRtcUi { return getInstance(context).also { sdk -> this.rtcUiMode?.let { mode -> - val defaultSuccessListener = - SuccessListener { Log.d(TAG, "$mode calls enabled.") } - val defaultErrorListener = - ErrorListener { Log.d(TAG, "Failed to enabled $mode calls.", it) } + val defaultSuccessListener = SuccessListener { Log.d(TAG, "$mode calls enabled.") } + val defaultErrorListener = ErrorListener { Log.d(TAG, "Failed to enabled $mode calls.", it) } when (mode) { RtcUiMode.CUSTOM -> { this.identity?.let { identity -> @@ -173,8 +199,7 @@ interface InfobipRtcUi { sdk.enableCalls( identity = identity, listenType = listenType, - successListener = mode.successListener - ?: defaultSuccessListener, + successListener = mode.successListener ?: defaultSuccessListener, errorListener = mode.errorListener ?: defaultErrorListener ) } @@ -330,4 +355,15 @@ interface InfobipRtcUi { * @param theme data object holding all theme attributes */ fun setTheme(theme: InfobipRtcUiTheme) + + /** + * Set custom error mapper which allows you to control what message is displayed to the user when certain error appears. + * InfobipRtcUi library uses default localised messages when mapper is not set. + * + * You can find all possible error codes defined by InfobipRtcUi library in [RtcUiError] class plus there + * are general WebRTC errors defined in Infobip WebRTC [documentation](https://www.infobip.com/docs/essentials/response-status-and-error-codes#webrtc-error-codes). + * + * @param errorMapper mapper to be used by InfobipRtcUi library + */ + fun setErrorMapper(errorMapper: RtcUiCallErrorMapper) } \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/RtcUiCallErrorMapper.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/RtcUiCallErrorMapper.kt new file mode 100644 index 00000000..f5afc752 --- /dev/null +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/RtcUiCallErrorMapper.kt @@ -0,0 +1,17 @@ +package com.infobip.webrtc.ui + +import com.infobip.webrtc.ui.model.RtcUiError + +fun interface RtcUiCallErrorMapper { + + /** + * The function converts [RtcUiError] to a message that will be displayed to the user. + * You can find all possible errors defined by InfobipRtcUi library in [RtcUiError.Companion] class plus there + * are general WebRTC error codes defined in Infobip [documentation](https://www.infobip.com/docs/essentials/response-status-and-error-codes#webrtc-error-codes). + * + * @param error InfobipRtcUi error + * @return message to be show to the user, if the message is null or blank, it is not shown to the user + */ + fun getMessageForError(error: RtcUiError): String? + +} \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/Cache.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/Cache.kt new file mode 100644 index 00000000..57688a90 --- /dev/null +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/Cache.kt @@ -0,0 +1,107 @@ +package com.infobip.webrtc.ui.internal.core + +import android.app.Activity +import com.infobip.webrtc.ui.RtcUiCallErrorMapper +import com.infobip.webrtc.ui.internal.model.RtcUiMode +import com.infobip.webrtc.ui.internal.ui.CallActivity +import com.infobip.webrtc.ui.model.InCallButton +import com.infobip.webrtc.ui.view.styles.Colors +import com.infobip.webrtc.ui.view.styles.Icons +import com.infobip.webrtc.ui.view.styles.IncomingCallMessageStyle +import com.infobip.webrtc.ui.view.styles.InfobipRtcUiTheme +import java.util.Locale + +//region Cache +internal interface Cache : CallRegistrationLifetimeCache, SdkLifetimeCache { + override fun clear() +} + +internal class InMemoryCache( + private val callsCache: CallRegistrationLifetimeCache = CallRegistrationLifetimeCacheImpl(), + sdkCache: SdkLifetimeCache = SdkLifetimeCacheImpl() +) : Cache, CallRegistrationLifetimeCache by callsCache, SdkLifetimeCache by sdkCache { + + override fun clear() { + callsCache.clear() + } + +} +//endregion + +//region CallsRegistrationLifetimeCache +/** + * Contains everything what has to be cached only for one calls registration ([InfobipRtcUi.enableCalls] - [InfobipRtcUi.disableCalls]). + * Cache is cleared when [InfobipRtcUi.disableCalls] is called. + */ +internal interface CallRegistrationLifetimeCache { + var identity: String + var rtcUiMode: RtcUiMode? + fun clear() +} + +internal class CallRegistrationLifetimeCacheImpl : CallRegistrationLifetimeCache { + + override var identity: String = "" + override var rtcUiMode: RtcUiMode? = null + + override fun clear() { + identity = "" + rtcUiMode = null + } +} +//endregion + +//region SdkLifetimeCache +/** + * Contains everything what has to be cached for whole SDK lifetime. + * Cache is cleared when SDK is removed from memory - app is killed. + */ +internal interface SdkLifetimeCache { + var configurationId: String + var activityClass: Class + var autoDeclineOnMissingNotificationPermission: Boolean + var autoDeclineOnMissingReadPhoneStatePermission: Boolean + var autoDeclineWhenOngoingCellularCall: Boolean + var autoFinishWhenIncomingCellularCallAccepted: Boolean + var locale: Locale? + var theme: InfobipRtcUiTheme? + val colors: Colors? + get() = theme?.colors + val icons: Icons? + get() = theme?.icons + val incomingCallMessageStyle: IncomingCallMessageStyle? + get() = theme?.incomingCallMessageStyle + var inCallButtons: List + var callErrorMapper: RtcUiCallErrorMapper? +} + +internal class SdkLifetimeCacheImpl : SdkLifetimeCache { + + override var configurationId: String = "" + override var activityClass: Class = CallActivity::class.java + override var autoDeclineOnMissingNotificationPermission: Boolean = true + override var autoDeclineOnMissingReadPhoneStatePermission: Boolean = false + override var autoDeclineWhenOngoingCellularCall: Boolean = true + override var autoFinishWhenIncomingCellularCallAccepted: Boolean = true + override var locale: Locale? = null + override var theme: InfobipRtcUiTheme? = null + override val colors: Colors? + get() = theme?.colors + override val icons: Icons? + get() = theme?.icons + override val incomingCallMessageStyle: IncomingCallMessageStyle? + get() = theme?.incomingCallMessageStyle + override var inCallButtons: List = emptyList() + get() = field.takeIf { it.isNotEmpty() } ?: listOf( + InCallButton.HangUp, + InCallButton.Mute(), + InCallButton.Video(), + InCallButton.Speaker(), + InCallButton.ScreenShare(), + InCallButton.FlipCam(), + ) + override var callErrorMapper: RtcUiCallErrorMapper? = null +} +//endregion + + diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/CallRegistrationWorker.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/CallRegistrationWorker.kt similarity index 98% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/CallRegistrationWorker.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/CallRegistrationWorker.kt index b3f5c496..dcbda9da 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/CallRegistrationWorker.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/CallRegistrationWorker.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc +package com.infobip.webrtc.ui.internal.core import android.annotation.TargetApi import android.app.Notification @@ -18,8 +18,8 @@ import androidx.work.WorkManager import androidx.work.WorkerParameters import com.infobip.webrtc.ui.InfobipRtcUi import com.infobip.webrtc.ui.R +import com.infobip.webrtc.ui.internal.model.RtcUiMode import com.infobip.webrtc.ui.model.ListenType -import com.infobip.webrtc.ui.model.RtcUiMode import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay @@ -27,7 +27,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine import java.util.concurrent.TimeUnit import kotlin.coroutines.resume -class CallRegistrationWorker( +internal class CallRegistrationWorker( private val context: Context, private val params: WorkerParameters ) : CoroutineWorker(context, params) { diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/InfobipRtcUiImpl.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/InfobipRtcUiImpl.kt similarity index 78% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/InfobipRtcUiImpl.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/InfobipRtcUiImpl.kt index 407e7a43..b5c40ccc 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/InfobipRtcUiImpl.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/InfobipRtcUiImpl.kt @@ -1,19 +1,19 @@ -package com.infobip.webrtc.ui +package com.infobip.webrtc.ui.internal.core import android.content.Context import android.util.Log -import com.infobip.webrtc.Cache -import com.infobip.webrtc.Injector -import com.infobip.webrtc.TAG -import com.infobip.webrtc.TokenProvider import com.infobip.webrtc.sdk.api.InfobipRTC import com.infobip.webrtc.sdk.api.model.push.Status -import com.infobip.webrtc.ui.delegate.CallsDelegate -import com.infobip.webrtc.ui.delegate.NotificationPermissionDelegate -import com.infobip.webrtc.ui.delegate.PushIdDelegate +import com.infobip.webrtc.ui.ErrorListener +import com.infobip.webrtc.ui.InfobipRtcUi +import com.infobip.webrtc.ui.RtcUiCallErrorMapper +import com.infobip.webrtc.ui.SuccessListener +import com.infobip.webrtc.ui.internal.delegate.CallsDelegate +import com.infobip.webrtc.ui.internal.delegate.NotificationPermissionDelegate +import com.infobip.webrtc.ui.internal.delegate.PushIdDelegate +import com.infobip.webrtc.ui.internal.model.RtcUiMode import com.infobip.webrtc.ui.model.InCallButton import com.infobip.webrtc.ui.model.ListenType -import com.infobip.webrtc.ui.model.RtcUiMode import com.infobip.webrtc.ui.view.styles.InfobipRtcUiTheme import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -23,14 +23,14 @@ import kotlinx.coroutines.withContext import java.util.Locale internal class InfobipRtcUiImpl( - private val context: Context, - private val tokenProvider: TokenProvider, - private val cache: Cache, - private val callsDelegate: CallsDelegate, - private val callsScope: CoroutineScope, - private val pushIdDelegate: PushIdDelegate, - private val rtcInstance: InfobipRTC, - private val notificationPermissionDelegate: NotificationPermissionDelegate, + private val context: Context, + private val tokenProvider: TokenProvider, + private val cache: Cache, + private val callsDelegate: CallsDelegate, + private val callsScope: CoroutineScope, + private val pushIdDelegate: PushIdDelegate, + private val rtcInstance: InfobipRTC, + private val notificationPermissionDelegate: NotificationPermissionDelegate, ) : InfobipRtcUi { private var rtcUiMode: RtcUiMode? @@ -49,7 +49,7 @@ internal class InfobipRtcUiImpl( cache.identity = identity tokenProvider.getToken(identity)?.let { token -> if (listenType == ListenType.PUSH) { - if (notificationPermissionDelegate.isPermissionNeeded()) { + if (!notificationPermissionDelegate.hasPermission()) { withContext(Dispatchers.Main) { notificationPermissionDelegate.request() } @@ -78,7 +78,10 @@ internal class InfobipRtcUiImpl( successListener = successListener, errorListener = errorListener ) - } ?: Log.d(TAG, "Could not obtain identity value(pushRegistrationId), waiting for broadcast.") + } ?: Log.d( + TAG, + "Could not obtain identity value(pushRegistrationId), waiting for broadcast." + ) } override fun enableInAppChatCalls( @@ -106,15 +109,19 @@ internal class InfobipRtcUiImpl( } override fun setLanguage(locale: Locale) { - Injector.locale = locale + cache.locale = locale } override fun setInCallButtons(buttons: List) { - Injector.inCallButtons = listOf(InCallButton.HangUp, *buttons.toTypedArray()) + cache.inCallButtons = listOf(InCallButton.HangUp, *buttons.toTypedArray()) } override fun setTheme(theme: InfobipRtcUiTheme) { - Injector.theme = theme + cache.theme = theme + } + + override fun setErrorMapper(errorMapper: RtcUiCallErrorMapper) { + cache.callErrorMapper = errorMapper } private fun registerPush(token: String, errorListener: ErrorListener?, successListener: SuccessListener?) { @@ -149,10 +156,6 @@ internal class InfobipRtcUiImpl( } private fun cleanStoredCallbacks() { - with(Injector.cache) { - rtcUiMode?.cleanListeners() - } + cache.rtcUiMode?.cleanListeners() } -} - - +} \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/Injector.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/Injector.kt similarity index 50% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/Injector.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/Injector.kt index 126a8b1f..7f5a5ba1 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/Injector.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/Injector.kt @@ -1,69 +1,51 @@ -package com.infobip.webrtc +package com.infobip.webrtc.ui.internal.core import android.content.Context import com.infobip.webrtc.sdk.api.InfobipRTC import com.infobip.webrtc.ui.InfobipRtcUi -import com.infobip.webrtc.ui.InfobipRtcUiImpl -import com.infobip.webrtc.ui.delegate.AppCodeDelegate -import com.infobip.webrtc.ui.delegate.AppCodeDelegateImpl -import com.infobip.webrtc.ui.delegate.CallsDelegate -import com.infobip.webrtc.ui.delegate.CallsDelegateImpl -import com.infobip.webrtc.ui.delegate.NotificationPermissionDelegate -import com.infobip.webrtc.ui.delegate.NotificationPermissionDelegateImpl -import com.infobip.webrtc.ui.delegate.PushIdDelegate -import com.infobip.webrtc.ui.delegate.PushIdDelegateImpl -import com.infobip.webrtc.ui.delegate.Vibrator -import com.infobip.webrtc.ui.delegate.VibratorImpl -import com.infobip.webrtc.ui.model.InCallButton -import com.infobip.webrtc.ui.notifications.CallNotificationFactory -import com.infobip.webrtc.ui.notifications.CallNotificationFactoryImpl -import com.infobip.webrtc.ui.view.styles.Colors -import com.infobip.webrtc.ui.view.styles.Icons -import com.infobip.webrtc.ui.view.styles.IncomingCallMessageStyle -import com.infobip.webrtc.ui.view.styles.InfobipRtcUiTheme +import com.infobip.webrtc.ui.internal.delegate.AppCodeDelegate +import com.infobip.webrtc.ui.internal.delegate.AppCodeDelegateImpl +import com.infobip.webrtc.ui.internal.delegate.CallsDelegate +import com.infobip.webrtc.ui.internal.delegate.CallsDelegateImpl +import com.infobip.webrtc.ui.internal.delegate.NotificationPermissionDelegate +import com.infobip.webrtc.ui.internal.delegate.NotificationPermissionDelegateImpl +import com.infobip.webrtc.ui.internal.delegate.PushIdDelegate +import com.infobip.webrtc.ui.internal.delegate.PushIdDelegateImpl +import com.infobip.webrtc.ui.internal.notification.CallNotificationFactory +import com.infobip.webrtc.ui.internal.notification.CallNotificationFactoryImpl import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import org.infobip.mobile.messaging.api.rtc.MobileApiRtc import org.infobip.mobile.messaging.mobileapi.MobileApiResourceProvider -import java.util.Locale internal const val TAG = "InfobipRtcUi" internal object Injector { + private lateinit var appContext: Context + private var webrtcUi: InfobipRtcUi? = null + private val callsScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private val rtcInstance = InfobipRTC.getInstance() - val vibrator: Vibrator by lazy { VibratorImpl(appContext) } - val cache: Cache = InMemoryCache() - val notificationFactory: CallNotificationFactory by lazy { CallNotificationFactoryImpl(appContext) } - val callsDelegate: CallsDelegate by lazy { CallsDelegateImpl(appContext, callsScope, rtcInstance) } - var theme: InfobipRtcUiTheme? = null - val colors: Colors? - get() = theme?.colors - val icons: Icons? - get() = theme?.icons - val incomingCallMessageStyle: IncomingCallMessageStyle? - get() = theme?.incomingCallMessageStyle - var locale: Locale? = null - var inCallButtons: List = listOf( - InCallButton.HangUp, - InCallButton.Mute(), - InCallButton.Video(), - InCallButton.Speaker(), - InCallButton.ScreenShare(), - InCallButton.FlipCam(), - ) - - private val rtcService: MobileApiRtc by lazy { MobileApiResourceProvider().getMobileApiRtc(appContext) } + private val rtcService: MobileApiRtc by lazy { MobileApiResourceProvider().getMobileApiRtc( + appContext + ) } private val tokenProvider: TokenProvider by lazy { TokenProviderImpl(rtcService) } private val pushIdDelegate: PushIdDelegate by lazy { PushIdDelegateImpl(appContext) } + + val cache: Cache = InMemoryCache() + val notificationFactory: CallNotificationFactory by lazy { CallNotificationFactoryImpl( + appContext + ) } + val callsDelegate: CallsDelegate by lazy { CallsDelegateImpl(appContext, rtcInstance) } val appCodeDelegate: AppCodeDelegate by lazy { AppCodeDelegateImpl(appContext) } - private var webrtcUi: InfobipRtcUi? = null - val notificationPermissionDelegate: NotificationPermissionDelegate by lazy { NotificationPermissionDelegateImpl(appContext) } + val notificationPermissionDelegate: NotificationPermissionDelegate by lazy { NotificationPermissionDelegateImpl( + appContext + ) } fun getWebrtcUi(context: Context): InfobipRtcUi { - if (!::appContext.isInitialized) + if (!Injector::appContext.isInitialized) appContext = context.applicationContext return webrtcUi ?: runCatching { InfobipRtcUiImpl( diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/RtcUiCallErrorMapperFactory.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/RtcUiCallErrorMapperFactory.kt new file mode 100644 index 00000000..930b887f --- /dev/null +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/RtcUiCallErrorMapperFactory.kt @@ -0,0 +1,31 @@ +package com.infobip.webrtc.ui.internal.core + +import android.content.Context +import com.infobip.webrtc.sdk.api.model.ErrorCode +import com.infobip.webrtc.ui.R +import com.infobip.webrtc.ui.RtcUiCallErrorMapper +import com.infobip.webrtc.ui.model.RtcUiError + +internal object RtcUiCallErrorMapperFactory { + fun create(context: Context): RtcUiCallErrorMapper { + return Injector.cache.callErrorMapper ?: InternalRtcUiCallErrorMapper(context) + } +} + +private class InternalRtcUiCallErrorMapper( + private val context: Context +) : RtcUiCallErrorMapper { + + override fun getMessageForError(error: RtcUiError): String? { + return when (error.name) { + RtcUiError.CELLULAR_CALL_ACCEPTED_WHILE_WEBRTC_CALL.name -> context.getString(R.string.mm_ongoing_cellular_call_call_finished) + RtcUiError.INCOMING_WEBRTC_CALL_WHILE_CELLULAR_CALL.name -> context.getString(R.string.mm_ongoing_cellular_call_declining_call) + RtcUiError.MISSING_READ_PHONE_STATE_PERMISSION.name -> context.getString(R.string.mm_read_phone_state_permission_required_declining_call) + RtcUiError.MISSING_POST_NOTIFICATIONS_PERMISSION.name -> context.getString(R.string.mm_notification_permission_required_declining_call) + ErrorCode.UNKNOWN.name -> context.getString(R.string.mm_unknown_error) + ErrorCode.NORMAL_HANGUP.name -> null + else -> error.description ?: error.name + } + } + +} \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/TokenProviderImpl.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/TokenProvider.kt similarity index 97% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/TokenProviderImpl.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/TokenProvider.kt index 033057ce..6345fa38 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/TokenProviderImpl.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/core/TokenProvider.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc +package com.infobip.webrtc.ui.internal.core import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/AppCodeDelegate.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/AppCodeDelegate.kt similarity index 84% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/AppCodeDelegate.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/AppCodeDelegate.kt index cceff703..518fb342 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/AppCodeDelegate.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/AppCodeDelegate.kt @@ -1,7 +1,6 @@ -package com.infobip.webrtc.ui.delegate +package com.infobip.webrtc.ui.internal.delegate import android.content.Context -import com.infobip.webrtc.Cache import org.infobip.mobile.messaging.MobileMessagingCore internal interface AppCodeDelegate { diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/CallsDelegate.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/CallsDelegate.kt similarity index 82% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/CallsDelegate.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/CallsDelegate.kt index 52b985dc..3c407d9f 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/CallsDelegate.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/CallsDelegate.kt @@ -1,8 +1,7 @@ -package com.infobip.webrtc.ui.delegate +package com.infobip.webrtc.ui.internal.delegate import android.content.Context import android.util.Log -import com.infobip.webrtc.TAG import com.infobip.webrtc.sdk.api.InfobipRTC import com.infobip.webrtc.sdk.api.call.ApplicationCall import com.infobip.webrtc.sdk.api.call.IncomingApplicationCall @@ -17,15 +16,15 @@ import com.infobip.webrtc.sdk.api.model.push.EnablePushNotificationResult import com.infobip.webrtc.sdk.api.model.video.RTCVideoTrack import com.infobip.webrtc.sdk.api.model.video.ScreenCapturer import com.infobip.webrtc.sdk.api.options.VideoOptions -import com.infobip.webrtc.ui.listeners.IncomingCallEventListenerImpl -import com.infobip.webrtc.ui.listeners.RtcUiCallEventListener -import com.infobip.webrtc.ui.model.CallState -import com.infobip.webrtc.ui.model.RtcUiAppCall -import com.infobip.webrtc.ui.model.RtcUiCall -import com.infobip.webrtc.ui.model.RtcUiIncomingAppCallImpl -import com.infobip.webrtc.ui.model.RtcUiIncomingCall -import com.infobip.webrtc.ui.model.RtcUiIncomingWebrtcCallImpl -import kotlinx.coroutines.CoroutineScope +import com.infobip.webrtc.ui.internal.core.TAG +import com.infobip.webrtc.ui.internal.listener.IncomingCallEventListenerImpl +import com.infobip.webrtc.ui.internal.listener.RtcUiCallEventListener +import com.infobip.webrtc.ui.internal.model.CallState +import com.infobip.webrtc.ui.internal.model.RtcUiAppCall +import com.infobip.webrtc.ui.internal.model.RtcUiCall +import com.infobip.webrtc.ui.internal.model.RtcUiIncomingAppCallImpl +import com.infobip.webrtc.ui.internal.model.RtcUiIncomingCall +import com.infobip.webrtc.ui.internal.model.RtcUiIncomingWebrtcCallImpl internal interface CallsDelegate { fun accept() @@ -57,7 +56,6 @@ internal interface CallsDelegate { internal class CallsDelegateImpl( private val context: Context, - private val callsScope: CoroutineScope, private val infobipRtc: InfobipRTC ) : CallsDelegate { private val call: RtcUiCall? @@ -157,11 +155,8 @@ internal class CallsDelegateImpl( override fun getCallState(): CallState? { return call?.let { call -> runCatching { - val remoteVideoTrack = remoteVideoTrack() - val screenShareTrack = screenShareTrack() - val localVideoTrack = localVideoTrack() CallState( - isIncoming = call.duration() == 0, + isEstablished = call.status()?.let { it == CallStatus.ESTABLISHED } == true || call.establishTime() != null || call.duration() > 0, isMuted = call.muted(), isPeerMuted = (call as? RtcUiAppCall)?.participants()?.firstOrNull()?.media?.audio?.muted, elapsedTimeSeconds = call.duration(), @@ -169,12 +164,12 @@ internal class CallsDelegateImpl( isLocalScreenShare = call.hasScreenShare(), callAlert = null, isPip = false, - isFinished = call.status()?.let { it == CallStatus.FINISHED || it == CallStatus.FINISHING } == true, + isFinished = call.status()?.let { it == CallStatus.FINISHED || it == CallStatus.FINISHING } == true || call.endTime() != null, showControls = true, - error = "", - localVideoTrack = localVideoTrack, - remoteVideoTrack = remoteVideoTrack, - screenShareTrack = screenShareTrack + error = null, + localVideoTrack = localVideoTrack(), + remoteVideoTrack = remoteVideoTrack(), + screenShareTrack = screenShareTrack() ) }.getOrNull() } @@ -188,10 +183,10 @@ internal class CallsDelegateImpl( Log.d(TAG, "Incoming call push message received $data") var handled = false if (infobipRtc.isIncomingCall(data)) { - infobipRtc.handleIncomingCall(data, context, IncomingCallEventListenerImpl(context, data, callsScope)) + infobipRtc.handleIncomingCall(data, context, IncomingCallEventListenerImpl(context, data)) handled = true } else if (infobipRtc.isIncomingApplicationCall(data)) { - infobipRtc.handleIncomingApplicationCall(data, context, IncomingCallEventListenerImpl(context, data, callsScope)) + infobipRtc.handleIncomingApplicationCall(data, context, IncomingCallEventListenerImpl(context, data)) handled = true } return handled @@ -201,12 +196,12 @@ internal class CallsDelegateImpl( infobipRtc.registerForActiveConnection( token, context, - IncomingCallEventListenerImpl(context, mapOf(), callsScope) as IncomingApplicationCallEventListener + IncomingCallEventListenerImpl(context, mapOf()) as IncomingApplicationCallEventListener ) infobipRtc.registerForActiveConnection( token, context, - IncomingCallEventListenerImpl(context, mapOf(), callsScope) as IncomingCallEventListener + IncomingCallEventListenerImpl(context, mapOf()) as IncomingCallEventListener ) } diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/NotificationPermissionDelegate.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/NotificationPermissionDelegate.kt similarity index 64% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/NotificationPermissionDelegate.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/NotificationPermissionDelegate.kt index 6261283d..31c749d7 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/NotificationPermissionDelegate.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/NotificationPermissionDelegate.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.delegate +package com.infobip.webrtc.ui.internal.delegate import android.Manifest.permission.POST_NOTIFICATIONS import android.annotation.SuppressLint @@ -10,15 +10,16 @@ import org.infobip.mobile.messaging.MobileMessaging internal interface NotificationPermissionDelegate { fun request() - fun isPermissionNeeded(): Boolean + fun hasPermission(): Boolean } -internal class NotificationPermissionDelegateImpl(private val context: Context) : NotificationPermissionDelegate { +internal class NotificationPermissionDelegateImpl(private val context: Context) : + NotificationPermissionDelegate { @RequiresApi(Build.VERSION_CODES.TIRAMISU) @SuppressLint("MissingPermission") override fun request() = MobileMessaging.getInstance(context).registerForRemoteNotifications() - override fun isPermissionNeeded(): Boolean { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && context.checkSelfPermission(POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED + override fun hasPermission(): Boolean { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || context.checkSelfPermission(POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED } } \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/PhoneStateDelegate.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/PhoneStateDelegate.kt new file mode 100644 index 00000000..9397e171 --- /dev/null +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/PhoneStateDelegate.kt @@ -0,0 +1,133 @@ +package com.infobip.webrtc.ui.internal.delegate + +import android.Manifest +import android.content.Context +import android.content.Context.TELEPHONY_SERVICE +import android.content.pm.PackageManager +import android.os.Build +import android.telephony.PhoneStateListener +import android.telephony.TelephonyCallback +import android.telephony.TelephonyManager +import androidx.annotation.RequiresApi +import androidx.annotation.RequiresPermission +import androidx.core.content.ContextCompat + +internal interface PhoneStateDelegate { + + /** + * If the value is true it is needed to request [Manifest.permission.READ_PHONE_STATE] permission before calling any another delegate's function. + */ + val requiresPhoneStatePermission: Boolean + + /** + * Returns current phone state defined by integer: + * -1 = Could not obtain call state because of missing [Manifest.permission.READ_PHONE_STATE] permission on API > 31 + * 0 = [TelephonyManager.CALL_STATE_IDLE] - there is no cellular call + * 1 = [TelephonyManager.CALL_STATE_RINGING] - there is ringing cellular call + * 2 = [TelephonyManager.CALL_STATE_OFFHOOK] - there is outgoing or ongoing cellular call + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE, conditional = true) + fun getState(): Int + + /** + * Registers phone state listener. + * On API > 31 [Manifest.permission.READ_PHONE_STATE] permission is needed to register listener. + * + * @param onStateChanged listener returns current phone state defined by integer: + * -1 = Could not obtain call state because of missing [Manifest.permission.READ_PHONE_STATE] permission on API > 31 + * 0 = [TelephonyManager.CALL_STATE_IDLE] - there is no cellular call + * 1 = [TelephonyManager.CALL_STATE_RINGING] - there is ringing cellular call + * 2 = [TelephonyManager.CALL_STATE_OFFHOOK] - there is outgoing or ongoing cellular call + * @return true if listener was registered, false otherwise + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE, conditional = true) + fun registerStateListener(onStateChanged: (Int) -> Unit): Boolean + + /** + * Unregisters previously registered listener. + */ + fun unregisterStateListener() +} + +internal object PhoneStateDelegateFactory { + + private var delegate: PhoneStateDelegate? = null + + fun getPhoneStateDelegate(context: Context): PhoneStateDelegate { + return delegate ?: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + Api31PhoneStateDelegate(context) + } else { + Api30PhoneStateDelegate(context) + }.also { delegate = it } + } + +} + +private class Api31PhoneStateDelegate( + private val context: Context +): PhoneStateDelegate { + + override val requiresPhoneStatePermission: Boolean = true + private val telephonyManager = context.getSystemService(TELEPHONY_SERVICE) as TelephonyManager + private var callback: StateCallback? = null + + @RequiresApi(Build.VERSION_CODES.S) + override fun getState(): Int { + return if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { + telephonyManager.callStateForSubscription + } else { + -1 + } + } + + @RequiresApi(Build.VERSION_CODES.S) + override fun registerStateListener(onStateChanged: (Int) -> Unit): Boolean { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { + callback = StateCallback { onStateChanged(it) } + telephonyManager.registerTelephonyCallback(context.mainExecutor, StateCallback { onStateChanged(it) }) + return true + } + return false + } + + @RequiresApi(Build.VERSION_CODES.S) + override fun unregisterStateListener() { + callback?.let { telephonyManager.unregisterTelephonyCallback(it) } + } + + @RequiresApi(api = Build.VERSION_CODES.S) + private class StateCallback( + private val onStateChanged: (Int) -> Unit + ) : TelephonyCallback(), TelephonyCallback.CallStateListener { + override fun onCallStateChanged(state: Int) { + onStateChanged(state) + } + } + +} + +@Suppress("DEPRECATION") +private class Api30PhoneStateDelegate( + context: Context +): PhoneStateDelegate { + + override val requiresPhoneStatePermission: Boolean = false + private val telephonyManager = context.getSystemService(TELEPHONY_SERVICE) as TelephonyManager + + override fun getState(): Int = telephonyManager.callState + + override fun registerStateListener(onStateChanged: (Int) -> Unit): Boolean { + telephonyManager.listen(object : PhoneStateListener() { + @Deprecated("Deprecated in Java") + override fun onCallStateChanged(state: Int, phoneNumber: String?) { + super.onCallStateChanged(state, phoneNumber) + onStateChanged(state) + } + }, PhoneStateListener.LISTEN_CALL_STATE) + return true + } + + override fun unregisterStateListener() { + } + +} \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/PushIdDelegate.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/PushIdDelegate.kt similarity index 85% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/PushIdDelegate.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/PushIdDelegate.kt index 3212b2a8..6fb29d7d 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/PushIdDelegate.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/PushIdDelegate.kt @@ -1,8 +1,8 @@ -package com.infobip.webrtc.ui.delegate +package com.infobip.webrtc.ui.internal.delegate import android.content.Context import android.util.Log -import com.infobip.webrtc.TAG +import com.infobip.webrtc.ui.internal.core.TAG import org.infobip.mobile.messaging.MobileMessaging internal interface PushIdDelegate { diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/Vibrator.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/Vibrator.kt similarity index 95% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/Vibrator.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/Vibrator.kt index 9f05ed93..2f830f4c 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/delegate/Vibrator.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/delegate/Vibrator.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.delegate +package com.infobip.webrtc.ui.internal.delegate import android.content.Context import android.os.Build @@ -41,7 +41,8 @@ internal class VibratorImpl(appContext: Context) : Vibrator { } @Suppress("DEPRECATION") - private class VibratorServiceImpl(appContext: Context, private val pattern: LongArray) : Vibrator { + private class VibratorServiceImpl(appContext: Context, private val pattern: LongArray) : + Vibrator { private val vibratorService: VibratorService? = appContext.getSystemService(Context.VIBRATOR_SERVICE) as? VibratorService override fun vibrate() { diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/listeners/DefaultRtcUiCallEventListener.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/listener/DefaultRtcUiCallEventListener.kt similarity index 97% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/listeners/DefaultRtcUiCallEventListener.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/listener/DefaultRtcUiCallEventListener.kt index e5a143b5..9ff2105f 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/listeners/DefaultRtcUiCallEventListener.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/listener/DefaultRtcUiCallEventListener.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.listeners +package com.infobip.webrtc.ui.internal.listener import com.infobip.webrtc.sdk.api.event.call.CallEarlyMediaEvent import com.infobip.webrtc.sdk.api.event.call.CallEstablishedEvent @@ -29,7 +29,7 @@ import com.infobip.webrtc.sdk.api.event.call.ScreenShareAddedEvent import com.infobip.webrtc.sdk.api.event.call.ScreenShareRemovedEvent import com.infobip.webrtc.sdk.api.model.ErrorCode -open class DefaultRtcUiCallEventListener : RtcUiCallEventListener { +internal open class DefaultRtcUiCallEventListener : RtcUiCallEventListener { override fun onRinging(callRingingEvent: CallRingingEvent?) {} override fun onEarlyMedia(callEarlyMediaEvent: CallEarlyMediaEvent?) {} diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/listeners/IncomingCallEventListenerImpl.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/listener/IncomingCallEventListenerImpl.kt similarity index 53% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/listeners/IncomingCallEventListenerImpl.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/listener/IncomingCallEventListenerImpl.kt index b920d46d..8251ea91 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/listeners/IncomingCallEventListenerImpl.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/listener/IncomingCallEventListenerImpl.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.listeners +package com.infobip.webrtc.ui.internal.listener import android.content.Context import android.content.Intent @@ -9,40 +9,32 @@ import com.infobip.webrtc.sdk.api.event.rtc.IncomingApplicationCallEvent import com.infobip.webrtc.sdk.api.event.rtc.IncomingWebrtcCallEvent import com.infobip.webrtc.sdk.api.model.CallStatus import com.infobip.webrtc.sdk.api.model.ErrorCode -import com.infobip.webrtc.ui.model.RtcUiIncomingAppCallImpl -import com.infobip.webrtc.ui.model.RtcUiIncomingCall -import com.infobip.webrtc.ui.model.RtcUiIncomingWebrtcCallImpl -import com.infobip.webrtc.ui.service.OngoingCallService -import com.infobip.webrtc.ui.service.OngoingCallService.Companion.CALL_STATUS_EXTRA -import com.infobip.webrtc.ui.service.OngoingCallService.Companion.INCOMING_CALL_ACTION -import com.infobip.webrtc.ui.service.OngoingCallService.Companion.NAME_EXTRA -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import com.infobip.webrtc.ui.internal.model.RtcUiIncomingAppCallImpl +import com.infobip.webrtc.ui.internal.model.RtcUiIncomingCall +import com.infobip.webrtc.ui.internal.model.RtcUiIncomingWebrtcCallImpl +import com.infobip.webrtc.ui.internal.service.OngoingCallService +import com.infobip.webrtc.ui.internal.service.OngoingCallService.Companion.CALL_STATUS_EXTRA +import com.infobip.webrtc.ui.internal.service.OngoingCallService.Companion.INCOMING_CALL_ACTION +import com.infobip.webrtc.ui.internal.service.OngoingCallService.Companion.NAME_EXTRA internal class IncomingCallEventListenerImpl( private val context: Context, private val pushPayload: Map, - private val callsScope: CoroutineScope ) : IncomingCallEventListener, IncomingApplicationCallEventListener { /** - Listener handles case when only ringing notification is present, without ActiveCallActivity. Once ActiveCallActivity is started - it replaces this listener with own one. + * Listener handles case when only ringing notification is present, without ActiveCallActivity. Once ActiveCallActivity is started + * it replaces this listener with own one. */ private val eventListener = object : DefaultRtcUiCallEventListener() { private fun stopCall() { - callsScope.launch(Dispatchers.Main) { - OngoingCallService.sendCallServiceIntent( - context, - OngoingCallService.CALL_ENDED_ACTION - ) - } + OngoingCallService.sendCallServiceIntent(context, OngoingCallService.CALL_ENDED_ACTION) } override fun onHangup(callHangupEvent: CallHangupEvent?) { stopCall() } + override fun onError(errorCode: ErrorCode?) { stopCall() } @@ -52,15 +44,13 @@ internal class IncomingCallEventListenerImpl( if (call.status() == CallStatus.FINISHING || call.status() == CallStatus.FINISHED) return - callsScope.launch(Dispatchers.Main) { - call.updateCustomData(pushPayload) - call.setEventListener(eventListener) - context.startService(Intent(context, OngoingCallService::class.java).apply { - action = INCOMING_CALL_ACTION - putExtra(NAME_EXTRA, call.peer(context)) - putExtra(CALL_STATUS_EXTRA, call.status()?.name) - }) - } + call.updateCustomData(pushPayload) + call.setEventListener(eventListener) + context.startService(Intent(context, OngoingCallService::class.java).apply { + action = INCOMING_CALL_ACTION + putExtra(NAME_EXTRA, call.peer(context)) + putExtra(CALL_STATUS_EXTRA, call.status()?.name) + }) } override fun onIncomingApplicationCall(incomingApplicationCallEvent: IncomingApplicationCallEvent?) { diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/listeners/RtcUiCallEventListener.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/listener/RtcUiCallEventListener.kt similarity index 97% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/listeners/RtcUiCallEventListener.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/listener/RtcUiCallEventListener.kt index 7ee4ffb9..1ab10299 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/listeners/RtcUiCallEventListener.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/listener/RtcUiCallEventListener.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.listeners +package com.infobip.webrtc.ui.internal.listener import com.infobip.webrtc.sdk.api.event.call.CallEarlyMediaEvent import com.infobip.webrtc.sdk.api.event.call.CallEstablishedEvent @@ -32,7 +32,7 @@ import com.infobip.webrtc.sdk.api.event.listener.ApplicationCallEventListener import com.infobip.webrtc.sdk.api.event.listener.WebrtcCallEventListener import com.infobip.webrtc.sdk.api.model.ErrorCode -interface RtcUiCallEventListener { +internal interface RtcUiCallEventListener { fun onRinging(callRingingEvent: CallRingingEvent?) fun onEarlyMedia(callEarlyMediaEvent: CallEarlyMediaEvent?) fun onEstablished(callEstablishedEvent: CallEstablishedEvent?) @@ -71,7 +71,7 @@ interface RtcUiCallEventListener { fun onReconnected(reconnectedEvent: ReconnectedEvent?) } -fun RtcUiCallEventListener.toWebRtcCallEventListener(): WebrtcCallEventListener { +internal fun RtcUiCallEventListener.toWebRtcCallEventListener(): WebrtcCallEventListener { return object : WebrtcCallEventListener { override fun onEarlyMedia(callEarlyMediaEvent: CallEarlyMediaEvent?) { this@toWebRtcCallEventListener.onEarlyMedia(callEarlyMediaEvent) @@ -139,7 +139,7 @@ fun RtcUiCallEventListener.toWebRtcCallEventListener(): WebrtcCallEventListener } } -fun RtcUiCallEventListener.toAppCallEventListener(): ApplicationCallEventListener { +internal fun RtcUiCallEventListener.toAppCallEventListener(): ApplicationCallEventListener { return object : ApplicationCallEventListener { override fun onEarlyMedia(callEarlyMediaEvent: CallEarlyMediaEvent?) { this@toAppCallEventListener.onEarlyMedia(callEarlyMediaEvent) diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/CallState.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/CallState.kt similarity index 81% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/CallState.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/CallState.kt index d9c0f004..6042db64 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/CallState.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/CallState.kt @@ -1,10 +1,10 @@ -package com.infobip.webrtc.ui.model +package com.infobip.webrtc.ui.internal.model import com.infobip.webrtc.sdk.api.model.video.RTCVideoTrack -import com.infobip.webrtc.ui.view.CallAlert +import com.infobip.webrtc.ui.internal.ui.view.CallAlert internal data class CallState( - val isIncoming: Boolean, + val isEstablished: Boolean, val isMuted: Boolean, val isPeerMuted: Boolean?, val elapsedTimeSeconds: Int, @@ -14,7 +14,7 @@ internal data class CallState( val isPip: Boolean, val isFinished: Boolean, val showControls: Boolean, - val error: String = "", + val error: String? = null, val localVideoTrack: RTCVideoTrack? = null, val remoteVideoTrack: RTCVideoTrack? = null, val screenShareTrack: RTCVideoTrack? = null diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiAppCall.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiAppCall.kt similarity index 93% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiAppCall.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiAppCall.kt index b8fcccf9..001545f0 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiAppCall.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiAppCall.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.model +package com.infobip.webrtc.ui.internal.model import android.content.Context import android.util.Log @@ -13,9 +13,9 @@ import com.infobip.webrtc.sdk.api.model.video.RTCVideoTrack import com.infobip.webrtc.sdk.api.model.video.ScreenCapturer import com.infobip.webrtc.sdk.api.options.VideoOptions import com.infobip.webrtc.ui.R -import com.infobip.webrtc.ui.utils.applyIf -import com.infobip.webrtc.ui.listeners.RtcUiCallEventListener -import com.infobip.webrtc.ui.listeners.toAppCallEventListener +import com.infobip.webrtc.ui.internal.listener.RtcUiCallEventListener +import com.infobip.webrtc.ui.internal.listener.toAppCallEventListener +import com.infobip.webrtc.ui.internal.utils.applyIf import java.util.* /** @@ -100,7 +100,8 @@ internal abstract class BaseRtcUiAppCall( internal interface RtcUiIncomingAppCall : RtcUiIncomingCall -internal class RtcUiIncomingAppCallImpl(private val activeCall: IncomingApplicationCall) : BaseRtcUiAppCall(activeCall), RtcUiIncomingAppCall { +internal class RtcUiIncomingAppCallImpl(private val activeCall: IncomingApplicationCall) : BaseRtcUiAppCall(activeCall), + RtcUiIncomingAppCall { override fun peer(context: Context): String { return activeCall.fromDisplayName()?.takeIf { it.isNotBlank() } diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiCall.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiCall.kt similarity index 94% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiCall.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiCall.kt index 70d530dd..01597e35 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiCall.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiCall.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.model +package com.infobip.webrtc.ui.internal.model import android.content.Context import com.infobip.webrtc.sdk.api.event.listener.NetworkQualityEventListener @@ -8,7 +8,7 @@ import com.infobip.webrtc.sdk.api.model.RemoteVideo import com.infobip.webrtc.sdk.api.model.video.RTCVideoTrack import com.infobip.webrtc.sdk.api.model.video.ScreenCapturer import com.infobip.webrtc.sdk.api.options.VideoOptions -import com.infobip.webrtc.ui.listeners.RtcUiCallEventListener +import com.infobip.webrtc.ui.internal.listener.RtcUiCallEventListener import java.util.* internal interface RtcUiCall { diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiCallOptions.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiCallOptions.kt similarity index 96% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiCallOptions.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiCallOptions.kt index 85b6be76..33d7311f 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiCallOptions.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiCallOptions.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.model +package com.infobip.webrtc.ui.internal.model import com.infobip.webrtc.sdk.api.options.ApplicationCallOptions import com.infobip.webrtc.sdk.api.options.AudioOptions diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiCallVideoTrackType.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiCallVideoTrackType.kt similarity index 68% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiCallVideoTrackType.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiCallVideoTrackType.kt index 0106bb93..7e631d3a 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiCallVideoTrackType.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiCallVideoTrackType.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.model +package com.infobip.webrtc.ui.internal.model internal enum class RtcUiCallVideoTrackType { CAMERA, diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiMode.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiMode.kt similarity index 93% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiMode.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiMode.kt index b6abba6a..8e60b7f1 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiMode.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiMode.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.model +package com.infobip.webrtc.ui.internal.model import com.infobip.webrtc.ui.ErrorListener import com.infobip.webrtc.ui.SuccessListener diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiWebrtcCall.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiWebrtcCall.kt similarity index 92% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiWebrtcCall.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiWebrtcCall.kt index b65c8e70..75b74159 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiWebrtcCall.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/model/RtcUiWebrtcCall.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.model +package com.infobip.webrtc.ui.internal.model import android.content.Context import android.util.Log @@ -11,10 +11,10 @@ import com.infobip.webrtc.sdk.api.model.video.RTCVideoTrack import com.infobip.webrtc.sdk.api.model.video.ScreenCapturer import com.infobip.webrtc.sdk.api.options.VideoOptions import com.infobip.webrtc.ui.R -import com.infobip.webrtc.ui.utils.applyIf -import com.infobip.webrtc.ui.listeners.RtcUiCallEventListener -import com.infobip.webrtc.ui.listeners.toWebRtcCallEventListener -import java.util.* +import com.infobip.webrtc.ui.internal.listener.RtcUiCallEventListener +import com.infobip.webrtc.ui.internal.listener.toWebRtcCallEventListener +import com.infobip.webrtc.ui.internal.utils.applyIf +import java.util.Date internal interface RtcUiWebrtcCall : RtcUiCall @@ -89,7 +89,8 @@ internal abstract class BaseRtcUiWebrtcCall( internal interface RtcUiIncomingWebRtcCall : RtcUiIncomingCall -internal class RtcUiIncomingWebrtcCallImpl(private val activeCall: IncomingWebrtcCall) : BaseRtcUiWebrtcCall(activeCall), RtcUiIncomingWebRtcCall { +internal class RtcUiIncomingWebrtcCallImpl(private val activeCall: IncomingWebrtcCall) : BaseRtcUiWebrtcCall(activeCall), + RtcUiIncomingWebRtcCall { override fun peer(context: Context): String { return activeCall.source()?.displayIdentifier()?.takeIf { it.isNotBlank() } diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/notifications/CallNotificationFactory.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/notification/CallNotificationFactory.kt similarity index 94% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/notifications/CallNotificationFactory.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/notification/CallNotificationFactory.kt index 6e01ba67..e4b5a873 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/notifications/CallNotificationFactory.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/notification/CallNotificationFactory.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.notifications +package com.infobip.webrtc.ui.internal.notification import android.app.Notification import android.app.NotificationChannel @@ -13,16 +13,16 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE import androidx.core.app.Person import androidx.core.content.getSystemService -import com.infobip.webrtc.Injector -import com.infobip.webrtc.ui.CallActivity import com.infobip.webrtc.ui.R -import com.infobip.webrtc.ui.service.OngoingCallService -import com.infobip.webrtc.ui.utils.resolveStyledStringAttribute +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.service.OngoingCallService +import com.infobip.webrtc.ui.internal.ui.CallActivity +import com.infobip.webrtc.ui.internal.utils.resolveStyledStringAttribute const val CALL_NOTIFICATION_ID = 9999 const val SCREEN_SHARE_NOTIFICATION_ID = 9998 -interface CallNotificationFactory { +internal interface CallNotificationFactory { fun createIncomingCallNotification( context: Context, callerName: String, @@ -117,9 +117,9 @@ internal class CallNotificationFactoryImpl( isSilent: Boolean, ): Notification { val themedContext by lazy { ContextThemeWrapper(context, R.style.InfobipRtcUi_Call) } - val incomeMessage: String? = Injector.incomingCallMessageStyle?.messageText ?: + val incomeMessage: String? = Injector.cache.incomingCallMessageStyle?.messageText ?: themedContext.resolveStyledStringAttribute(R.styleable.InfobipRtcUi_rtc_ui_incoming_call_message, R.attr.infobipRtcUiStyle, R.styleable.InfobipRtcUi) - val incomeHeadline: String? = Injector.incomingCallMessageStyle?.headlineText ?: + val incomeHeadline: String? = Injector.cache.incomingCallMessageStyle?.headlineText ?: themedContext.resolveStyledStringAttribute(R.styleable.InfobipRtcUi_rtc_ui_incoming_call_headline, R.attr.infobipRtcUiStyle, R.styleable.InfobipRtcUi) val acceptCall = incomeMessage.isNullOrEmpty() && incomeHeadline.isNullOrEmpty() diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/receiver/LcRegIdBroadcastReceiver.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/receiver/LcRegIdBroadcastReceiver.kt similarity index 71% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/receiver/LcRegIdBroadcastReceiver.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/receiver/LcRegIdBroadcastReceiver.kt index 451e4812..f7f82924 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/receiver/LcRegIdBroadcastReceiver.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/receiver/LcRegIdBroadcastReceiver.kt @@ -1,11 +1,11 @@ -package com.infobip.webrtc.ui.receiver +package com.infobip.webrtc.ui.internal.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import com.infobip.webrtc.CallRegistrationWorker -import com.infobip.webrtc.Injector -import com.infobip.webrtc.ui.model.RtcUiMode +import com.infobip.webrtc.ui.internal.core.CallRegistrationWorker +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.model.RtcUiMode import org.infobip.mobile.messaging.BroadcastParameter.EXTRA_LIVECHAT_REGISTRATION_ID class LcRegIdBroadcastReceiver: BroadcastReceiver() { diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/receiver/PhoneStateBroadcastReceiver.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/receiver/PhoneStateBroadcastReceiver.kt new file mode 100644 index 00000000..b4efb557 --- /dev/null +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/receiver/PhoneStateBroadcastReceiver.kt @@ -0,0 +1,47 @@ +package com.infobip.webrtc.ui.internal.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.telephony.TelephonyManager +import android.util.Log +import android.widget.Toast +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.core.RtcUiCallErrorMapperFactory +import com.infobip.webrtc.ui.internal.core.TAG +import com.infobip.webrtc.ui.internal.service.OngoingCallService +import com.infobip.webrtc.ui.model.RtcUiError + +class PhoneStateBroadcastReceiver : BroadcastReceiver() { + + private val callDelegate by lazy { Injector.callsDelegate } + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == TelephonyManager.ACTION_PHONE_STATE_CHANGED) { + val phoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE) + if (phoneState != null) { + Log.d(TAG, "Phone state has changed: $phoneState") + when (phoneState) { + TelephonyManager.EXTRA_STATE_RINGING -> {} // Incoming call ringing + + TelephonyManager.EXTRA_STATE_OFFHOOK -> { // Call ongoing + callDelegate.getCallState()?.let { rtcCall -> + if (!rtcCall.isFinished && Injector.cache.autoFinishWhenIncomingCellularCallAccepted){ + if (rtcCall.isEstablished) + callDelegate.hangup() + else + callDelegate.decline() + OngoingCallService.sendCallServiceIntent(context, OngoingCallService.CALL_ENDED_ACTION) + val message = RtcUiCallErrorMapperFactory.create(context).getMessageForError(RtcUiError.CELLULAR_CALL_ACCEPTED_WHILE_WEBRTC_CALL) + if (message?.isNotBlank() == true) { + Toast.makeText(context, message, Toast.LENGTH_LONG).show() + } + } + } + } + TelephonyManager.EXTRA_STATE_IDLE -> {} // Call ended or idle + } + } + } + } +} \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/receiver/PushIdBroadcastReceiver.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/receiver/PushIdBroadcastReceiver.kt similarity index 70% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/receiver/PushIdBroadcastReceiver.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/receiver/PushIdBroadcastReceiver.kt index a8370fa0..2f9f7ce3 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/receiver/PushIdBroadcastReceiver.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/receiver/PushIdBroadcastReceiver.kt @@ -1,11 +1,11 @@ -package com.infobip.webrtc.ui.receiver +package com.infobip.webrtc.ui.internal.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import com.infobip.webrtc.CallRegistrationWorker -import com.infobip.webrtc.Injector -import com.infobip.webrtc.ui.model.RtcUiMode +import com.infobip.webrtc.ui.internal.core.CallRegistrationWorker +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.model.RtcUiMode import org.infobip.mobile.messaging.BroadcastParameter.EXTRA_INFOBIP_ID class PushIdBroadcastReceiver : BroadcastReceiver() { diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/BaseService.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/BaseService.kt new file mode 100644 index 00000000..1750a8b7 --- /dev/null +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/BaseService.kt @@ -0,0 +1,15 @@ +package com.infobip.webrtc.ui.internal.service + +import android.app.Service +import android.content.Context +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.utils.applyLocale + +abstract class BaseService: Service() { + + override fun attachBaseContext(newBase: Context?) { + val newContext = Injector.cache.locale?.let { newBase?.applyLocale(it) } ?: newBase + super.attachBaseContext(newContext) + } + +} \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/DefaultIncomingCallService.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/DefaultIncomingCallService.kt similarity index 75% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/DefaultIncomingCallService.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/DefaultIncomingCallService.kt index 21b43176..d3ec8291 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/DefaultIncomingCallService.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/DefaultIncomingCallService.kt @@ -1,7 +1,8 @@ -package com.infobip.webrtc.ui.service +package com.infobip.webrtc.ui.internal.service import android.annotation.SuppressLint import com.google.firebase.messaging.RemoteMessage +import com.infobip.webrtc.ui.service.IncomingCallService @SuppressLint("MissingFirebaseInstanceTokenRefresh") class DefaultIncomingCallService : IncomingCallService() { diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/OngoingCallService.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/OngoingCallService.kt similarity index 62% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/OngoingCallService.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/OngoingCallService.kt index cecf5ae0..f6370ef9 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/OngoingCallService.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/OngoingCallService.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.service +package com.infobip.webrtc.ui.internal.service import android.app.Notification import android.content.Context @@ -13,17 +13,23 @@ import android.os.Build import android.os.IBinder import android.util.Log import android.widget.Toast -import com.infobip.webrtc.Cache -import com.infobip.webrtc.Injector -import com.infobip.webrtc.TAG +import androidx.core.app.ServiceCompat import com.infobip.webrtc.sdk.api.model.CallStatus import com.infobip.webrtc.ui.R -import com.infobip.webrtc.ui.delegate.CallsDelegate -import com.infobip.webrtc.ui.delegate.NotificationPermissionDelegate -import com.infobip.webrtc.ui.delegate.Vibrator -import com.infobip.webrtc.ui.notifications.CALL_NOTIFICATION_ID -import com.infobip.webrtc.ui.notifications.CallNotificationFactory -import com.infobip.webrtc.ui.utils.stopForegroundRemove +import com.infobip.webrtc.ui.RtcUiCallErrorMapper +import com.infobip.webrtc.ui.internal.core.Cache +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.core.RtcUiCallErrorMapperFactory +import com.infobip.webrtc.ui.internal.core.TAG +import com.infobip.webrtc.ui.internal.delegate.CallsDelegate +import com.infobip.webrtc.ui.internal.delegate.NotificationPermissionDelegate +import com.infobip.webrtc.ui.internal.delegate.PhoneStateDelegate +import com.infobip.webrtc.ui.internal.delegate.PhoneStateDelegateFactory +import com.infobip.webrtc.ui.internal.delegate.Vibrator +import com.infobip.webrtc.ui.internal.delegate.VibratorImpl +import com.infobip.webrtc.ui.internal.notification.CALL_NOTIFICATION_ID +import com.infobip.webrtc.ui.internal.notification.CallNotificationFactory +import com.infobip.webrtc.ui.model.RtcUiError class OngoingCallService : BaseService() { @@ -46,17 +52,21 @@ class OngoingCallService : BaseService() { const val CALL_STATUS_EXTRA = "com.infobip.calls.ui.service.OngoingCallService.CALL_STATUS_EXTRA" } - private val vibrateDelegate: Vibrator by lazy { Injector.vibrator } - + private val vibrateDelegate: Vibrator by lazy { VibratorImpl(this@OngoingCallService) } private val incomingCallRingtone: Ringtone by lazy { RingtoneManager.getRingtone(applicationContext, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)) } private val audioManager: AudioManager by lazy { applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager } private val notificationHelper: CallNotificationFactory by lazy { Injector.notificationFactory } private val callsDelegate: CallsDelegate by lazy { Injector.callsDelegate } private val notificationPermissionDelegate: NotificationPermissionDelegate by lazy { Injector.notificationPermissionDelegate } - private val cache: Cache = Injector.cache + private val hasNotificationPermission + get() = notificationPermissionDelegate.hasPermission() + private val phoneStateDelegate: PhoneStateDelegate by lazy { PhoneStateDelegateFactory.getPhoneStateDelegate(this@OngoingCallService) } + private val cache: Cache by lazy { Injector.cache } + private val errorMapper: RtcUiCallErrorMapper by lazy { RtcUiCallErrorMapperFactory.create(this@OngoingCallService) } + private var peerName: String = "" private var reconnectingTonePlayer: MediaPlayer? = null - + private var isCallActive: Boolean = false override fun onBind(intent: Intent?): IBinder? = null @@ -64,15 +74,26 @@ class OngoingCallService : BaseService() { Log.d(TAG, "Handle action: ${intent?.action.orEmpty()}") when (intent?.action) { INCOMING_CALL_ACTION -> { - val activeCallStatus = runCatching { CallStatus.valueOf(intent.getStringExtra(CALL_STATUS_EXTRA).orEmpty()) }.getOrDefault(CallStatus.FINISHED) - val isPermissionNeeded = notificationPermissionDelegate.isPermissionNeeded() - if (activeCallStatus != CallStatus.FINISHED && activeCallStatus != CallStatus.FINISHING && !isPermissionNeeded) { + val activeCallStatus = runCatching { CallStatus.valueOf(intent.getStringExtra( + CALL_STATUS_EXTRA + ).orEmpty()) }.getOrDefault(CallStatus.FINISHED) + val phoneState = phoneStateDelegate.getState() + + if (phoneState == -1 && cache.autoDeclineOnMissingReadPhoneStatePermission) { //could not get phone state because of missing permission + showToast(RtcUiError.MISSING_READ_PHONE_STATE_PERMISSION) + callsDelegate.decline() + } else if (phoneState > 0 && cache.autoDeclineWhenOngoingCellularCall) { //there is ongoing or ringing cellular phone call + showToast(RtcUiError.INCOMING_WEBRTC_CALL_WHILE_CELLULAR_CALL) + callsDelegate.decline() + } else if (!hasNotificationPermission && cache.autoDeclineOnMissingNotificationPermission) { + showToast(RtcUiError.MISSING_POST_NOTIFICATIONS_PERMISSION) + callsDelegate.decline() + } else if (activeCallStatus != CallStatus.FINISHED && activeCallStatus != CallStatus.FINISHING && hasNotificationPermission) { peerName = intent.getStringExtra(NAME_EXTRA) ?: applicationContext.getString(R.string.mm_unknown) startForeground(notificationHelper.createIncomingCallNotification(this, peerName, getString(R.string.mm_incoming_call))) startMedia() - } else if (isPermissionNeeded && cache.autoDeclineOnMissingNotificationPermission) { - Toast.makeText(applicationContext, getString(R.string.mm_notification_permission_required_declining_call), Toast.LENGTH_LONG).show() - callsDelegate.decline() + } else { + Log.e(TAG, "Incoming call not handled! callStatus=$activeCallStatus, hasNotificationPermission=$hasNotificationPermission") } } @@ -87,6 +108,7 @@ class OngoingCallService : BaseService() { CALL_ESTABLISHED_ACTION -> { stopMedia() startForeground(notificationHelper.createOngoingCallNotification(this, peerName, getString(R.string.mm_in_call))) + isCallActive = true } CALL_DECLINED_ACTION -> { @@ -96,7 +118,7 @@ class OngoingCallService : BaseService() { CALL_HANGUP_ACTION -> { callsDelegate.hangup() - stop() + onCallEnded() } CALL_RECONNECTING_ACTION -> { @@ -113,23 +135,28 @@ class OngoingCallService : BaseService() { } private fun startForeground(notification: Notification) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - startForeground(CALL_NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL) - } else { - startForeground(CALL_NOTIFICATION_ID, notification) + if (hasNotificationPermission) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + startForeground(CALL_NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL) + } else { + startForeground(CALL_NOTIFICATION_ID, notification) + } } } - private fun stop() { - stopForegroundRemove() - stopSelf() - } - private fun onCallEnded() { - ScreenShareService.sendScreenShareServiceIntent(applicationContext, ScreenShareService.ACTION_STOP_SCREEN_SHARE) + ScreenShareService.sendScreenShareServiceIntent( + applicationContext, + ScreenShareService.ACTION_STOP_SCREEN_SHARE + ) stopMedia() stopReconnectingTone() - stop() + if (isCallActive){ + playCallFinishedTone() + isCallActive = false + } + ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) + stopSelf() } private fun startMedia() { @@ -176,7 +203,7 @@ class OngoingCallService : BaseService() { runCatching { createMediaPlayer( - R.raw.reconnecting, + R.raw.reconnected, AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING, AudioManager.STREAM_VOICE_CALL, false @@ -192,6 +219,25 @@ class OngoingCallService : BaseService() { } } + private fun playCallFinishedTone() { + runCatching { + createMediaPlayer( + R.raw.finished, + AudioAttributes.USAGE_NOTIFICATION, + AudioManager.STREAM_VOICE_CALL, + false + ).apply { + setOnCompletionListener { + releaseSafely() + } + prepare() + start() + } + }.onFailure { + Log.e(TAG, "playCallFinishedTone() failed") + } + } + private fun createMediaPlayer( soundId: Int, usage: Int, @@ -219,4 +265,11 @@ class OngoingCallService : BaseService() { Log.e(TAG, "stop() and released() failed") } } + + private fun showToast(errorCode: RtcUiError) { + val message = errorMapper.getMessageForError(errorCode) + if (message?.isNotBlank() == true) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show() + } + } } \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/ScreenShareService.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/ScreenShareService.kt similarity index 82% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/ScreenShareService.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/ScreenShareService.kt index b7396fa9..3ba71d25 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/ScreenShareService.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/service/ScreenShareService.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.service +package com.infobip.webrtc.ui.internal.service import android.content.Context import android.content.Intent @@ -6,11 +6,11 @@ import android.content.pm.ServiceInfo import android.os.Build import android.os.IBinder import android.util.Log -import com.infobip.webrtc.Injector -import com.infobip.webrtc.TAG -import com.infobip.webrtc.ui.notifications.CallNotificationFactory -import com.infobip.webrtc.ui.notifications.SCREEN_SHARE_NOTIFICATION_ID -import com.infobip.webrtc.ui.utils.stopForegroundRemove +import androidx.core.app.ServiceCompat +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.core.TAG +import com.infobip.webrtc.ui.internal.notification.CallNotificationFactory +import com.infobip.webrtc.ui.internal.notification.SCREEN_SHARE_NOTIFICATION_ID class ScreenShareService : BaseService() { private val notificationHelper: CallNotificationFactory by lazy { Injector.notificationFactory } @@ -53,7 +53,7 @@ class ScreenShareService : BaseService() { } ACTION_STOP_SCREEN_SHARE -> { - stopForegroundRemove() + ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) stopSelf() isRunning = false } diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/CallActivity.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/CallActivity.kt similarity index 55% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/CallActivity.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/CallActivity.kt index fdba030a..71890d2c 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/CallActivity.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/CallActivity.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui +package com.infobip.webrtc.ui.internal.ui import android.Manifest import android.content.Context @@ -17,12 +17,11 @@ import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope -import com.infobip.webrtc.Injector -import com.infobip.webrtc.TAG -import com.infobip.webrtc.sdk.api.event.call.CallEarlyMediaEvent import com.infobip.webrtc.sdk.api.event.call.CallEstablishedEvent import com.infobip.webrtc.sdk.api.event.call.CallHangupEvent import com.infobip.webrtc.sdk.api.event.call.CallRingingEvent @@ -40,26 +39,38 @@ import com.infobip.webrtc.sdk.api.event.call.ReconnectingEvent import com.infobip.webrtc.sdk.api.event.call.ScreenShareAddedEvent import com.infobip.webrtc.sdk.api.event.call.ScreenShareRemovedEvent import com.infobip.webrtc.sdk.api.model.ErrorCode -import com.infobip.webrtc.ui.fragments.InCallFragment -import com.infobip.webrtc.ui.fragments.IncomingCallFragment -import com.infobip.webrtc.ui.listeners.DefaultRtcUiCallEventListener -import com.infobip.webrtc.ui.model.CallState -import com.infobip.webrtc.ui.service.OngoingCallService -import com.infobip.webrtc.ui.service.ScreenShareService -import com.infobip.webrtc.ui.utils.applyLocale -import com.infobip.webrtc.ui.utils.navigate -import com.infobip.webrtc.ui.view.CallAlert +import com.infobip.webrtc.ui.R +import com.infobip.webrtc.ui.RtcUiCallErrorMapper +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.core.RtcUiCallErrorMapperFactory +import com.infobip.webrtc.ui.internal.core.TAG +import com.infobip.webrtc.ui.internal.delegate.PhoneStateDelegate +import com.infobip.webrtc.ui.internal.delegate.PhoneStateDelegateFactory +import com.infobip.webrtc.ui.internal.listener.DefaultRtcUiCallEventListener +import com.infobip.webrtc.ui.internal.service.OngoingCallService +import com.infobip.webrtc.ui.internal.ui.fragment.InCallFragment +import com.infobip.webrtc.ui.internal.ui.fragment.IncomingCallFragment +import com.infobip.webrtc.ui.internal.ui.view.CallAlert +import com.infobip.webrtc.ui.internal.utils.applyLocale +import com.infobip.webrtc.ui.internal.utils.navigate +import com.infobip.webrtc.ui.internal.utils.throttleFirst +import com.infobip.webrtc.ui.model.RtcUiError import com.infobip.webrtc.ui.view.styles.Colors import com.infobip.webrtc.ui.view.styles.Icons import com.infobip.webrtc.ui.view.styles.IncomingCallMessageStyle import com.infobip.webrtc.ui.view.styles.InfobipRtcUiTheme import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach class CallActivity : AppCompatActivity(R.layout.activity_call) { + companion object { private const val CALLER_EXTRA_KEY = "CALLER_EXTRA_KEY" private const val ACCEPT_EXTRA_KEY = "ACCEPT_EXTRA_KEY" @@ -74,17 +85,27 @@ class CallActivity : AppCompatActivity(R.layout.activity_call) { } private val viewModel: CallViewModel by viewModels() - private val requestAudioPermissionLauncher = - registerForActivityResult( - ActivityResultContracts.RequestPermission() - ) { isGranted: Boolean -> - if (!isGranted) { - showAudioRationale() + private val phoneStateDelegate: PhoneStateDelegate by lazy { PhoneStateDelegateFactory.getPhoneStateDelegate(this@CallActivity) } + private val errorMapper: RtcUiCallErrorMapper by lazy { RtcUiCallErrorMapperFactory.create(this@CallActivity) } + private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result: Map -> + if (result[Manifest.permission.RECORD_AUDIO] == false) { + /** + * Starting in Android 11 (API level 30), if the user taps Deny for a specific permission more than once + * during your app's lifetime of installation on a device, the user doesn't see the system permissions + * dialog if your app requests that permission again. + * https://developer.android.com/training/permissions/requesting#handle-denial + */ + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.R || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) { + showRationale(Manifest.permission.RECORD_AUDIO) //ask permission again or hangup call + } else { + val errorMsg = "${getString(R.string.mm_audio_permission_required)} ${getString(R.string.mm_call_finished)}." + viewModel.endCall(errorMsg) + } } } override fun attachBaseContext(newBase: Context) { - val newContext = Injector.locale?.let { newBase.applyLocale(it) } ?: newBase + val newContext = Injector.cache.locale?.let { newBase.applyLocale(it) } ?: newBase super.attachBaseContext(newContext) } @@ -94,25 +115,14 @@ class CallActivity : AppCompatActivity(R.layout.activity_call) { context: Context, attrs: AttributeSet ): View? { - Injector.theme = InfobipRtcUiTheme ( - colors = Injector.colors ?: Colors(this, attrs), - icons = Injector.icons ?: Icons(this, attrs), - incomingCallMessageStyle = Injector.incomingCallMessageStyle ?: IncomingCallMessageStyle(this, attrs) + Injector.cache.theme = InfobipRtcUiTheme( + colors = Injector.cache.colors ?: Colors(this, attrs), + icons = Injector.cache.icons ?: Icons(this, attrs), + incomingCallMessageStyle = Injector.cache.incomingCallMessageStyle ?: IncomingCallMessageStyle(this, attrs) ) return super.onCreateView(parent, name, context, attrs) } - private fun renderState(state: CallState) { - with(state) { - if (error.isNotEmpty()) { - showError(error) - } - if (isFinished) { - showFinishCall() - } - } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.orientation = resources.configuration.orientation @@ -121,41 +131,94 @@ class CallActivity : AppCompatActivity(R.layout.activity_call) { listenCallEvents() applyArguments() showFragment() + subscribeCallState() + requestPermissions() + } + + private fun subscribeCallState() { + val messages = Channel(Channel.BUFFERED) + + messages.consumeAsFlow() + .filter { it.isNotBlank() } + .throttleFirst(3500) //duration of Toast.LENGTH_LONG + .onEach { Toast.makeText(this@CallActivity, it, Toast.LENGTH_LONG).show() } + .flowOn(Dispatchers.Main) + .flowWithLifecycle(lifecycle, Lifecycle.State.CREATED) + .catch { Log.e(TAG, "Call messages flow error.", it) } + .launchIn(lifecycleScope) + viewModel.state - .onEach(::renderState) + .onEach { state -> + val message = state.error?.takeIf { it.isNotBlank() } + ?: getString(R.string.mm_call_finished).takeIf { state.isFinished && phoneStateDelegate.getState() != 2 } //To not replace toast from PhoneStateBroadcastReceiver + message?.let { + messages.trySend(message) + viewModel.cleanError() + } + if (state.isFinished) + finishAndHideNotifications() + } .flowOn(Dispatchers.Main) .flowWithLifecycle(lifecycle) + .catch { Log.e(TAG, "Call state flow error.", it) } .launchIn(lifecycleScope) + } + private fun requestPermissions() { + val permissionsToRequest = mutableListOf() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val permission = Manifest.permission.RECORD_AUDIO when { - ContextCompat.checkSelfPermission( - this, - Manifest.permission.RECORD_AUDIO - ) == PackageManager.PERMISSION_GRANTED -> Log.d(TAG, "Audio permission granted") - - shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO) -> showAudioRationale() - else -> requestAudioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO) + ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -> Log.d(TAG, "$permission permission granted") + ActivityCompat.shouldShowRequestPermissionRationale(this, permission) -> showRationale(permission) + else -> permissionsToRequest.add(permission) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val btPermission = Manifest.permission.BLUETOOTH_CONNECT + when { + ContextCompat.checkSelfPermission(this, btPermission) == PackageManager.PERMISSION_GRANTED -> Log.d(TAG, "$btPermission permission granted") + ActivityCompat.shouldShowRequestPermissionRationale(this, btPermission) -> showRationale(btPermission) + else -> permissionsToRequest.add(btPermission) + } + val phoneStatePermission = Manifest.permission.READ_PHONE_STATE + when { + ContextCompat.checkSelfPermission(this, phoneStatePermission) == PackageManager.PERMISSION_GRANTED -> Log.d(TAG, "$phoneStatePermission permission granted") + ActivityCompat.shouldShowRequestPermissionRationale(this, phoneStatePermission) -> showRationale(phoneStatePermission) + else -> permissionsToRequest.add(phoneStatePermission) } } + if (permissionsToRequest.isNotEmpty()) + requestPermissionLauncher.launch(permissionsToRequest.toTypedArray()) } - private fun showAudioRationale() { - AlertDialog.Builder(this) - .setMessage(R.string.mm_audio_permission_required) - .setCancelable(false) - .setPositiveButton(R.string.mm_ok) { _, _ -> - requestAudioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO) - }.setNegativeButton(R.string.mm_cancel) { _, _ -> - viewModel.endCall() - }.show() + private fun showRationale(permission: String) { + val messageRes = when (permission) { + Manifest.permission.RECORD_AUDIO -> R.string.mm_audio_permission_required + Manifest.permission.BLUETOOTH_CONNECT -> R.string.mm_bt_connect_permission_required + Manifest.permission.READ_PHONE_STATE -> R.string.mm_read_phone_state_permission_required + else -> null + } + + if (messageRes != null) { + AlertDialog.Builder(this) + .setMessage(messageRes) + .setCancelable(false) + .setPositiveButton(R.string.mm_ok) { _, _ -> + requestPermissionLauncher.launch(arrayOf(permission)) + }.setNegativeButton(R.string.mm_cancel) { _, _ -> + if (permission == Manifest.permission.RECORD_AUDIO) { + viewModel.endCall() + } + }.show() + } } private fun showFragment() { - if (viewModel.isIncomingCall()) { - supportFragmentManager.navigate(IncomingCallFragment(), R.id.navHost) - } else { + if (viewModel.isEstablished()) { supportFragmentManager.navigate(InCallFragment(), R.id.navHost) + } else { + supportFragmentManager.navigate(IncomingCallFragment(), R.id.navHost) } } @@ -164,7 +227,7 @@ class CallActivity : AppCompatActivity(R.layout.activity_call) { val accept = intent.getBooleanExtra(ACCEPT_EXTRA_KEY, false) viewModel.peerName = peer - if (viewModel.isIncomingCall()) { + if (!viewModel.isEstablished()) { val action = if (accept) { viewModel.accept() OngoingCallService.CALL_ESTABLISHED_ACTION @@ -175,26 +238,8 @@ class CallActivity : AppCompatActivity(R.layout.activity_call) { } } - private fun showError(message: String) { - Toast.makeText(this, message, Toast.LENGTH_LONG).show() - if (viewModel.state.value.isFinished) - finishAndHideNotifications() - } - - private fun showFinishCall() { - Toast.makeText(this, R.string.mm_call_finished, Toast.LENGTH_LONG).show() - finishAndHideNotifications() - } - private fun finishAndHideNotifications() { - OngoingCallService.sendCallServiceIntent( - applicationContext, - OngoingCallService.CALL_ENDED_ACTION - ) - ScreenShareService.sendScreenShareServiceIntent( - applicationContext, - ScreenShareService.ACTION_STOP_SCREEN_SHARE - ) + OngoingCallService.sendCallServiceIntent(applicationContext, OngoingCallService.CALL_ENDED_ACTION) finishAndRemoveTask() } @@ -215,8 +260,8 @@ class CallActivity : AppCompatActivity(R.layout.activity_call) { viewModel.setEventListener(object : DefaultRtcUiCallEventListener() { override fun onError(errorCode: ErrorCode?) { - viewModel.onError(errorCode?.let { it.description ?: it.name } - ?: getString(R.string.mm_unknown_error)) + val message = errorMapper.getMessageForError(RtcUiError(errorCode ?: ErrorCode.UNKNOWN)) + viewModel.onError(message) } override fun onCameraVideoAdded(cameraVideoAddedEvent: CameraVideoAddedEvent?) { @@ -275,55 +320,34 @@ class CallActivity : AppCompatActivity(R.layout.activity_call) { } override fun onRinging(callRingingEvent: CallRingingEvent?) { - viewModel.updateState { copy(isIncoming = true) } runOnUiThread { - OngoingCallService.sendCallServiceIntent( - applicationContext, - OngoingCallService.INCOMING_CALL_ACTION - ) - } - } - - override fun onEarlyMedia(callEarlyMediaEvent: CallEarlyMediaEvent?) { - runOnUiThread { - OngoingCallService.sendCallServiceIntent( - applicationContext, - OngoingCallService.CALL_ESTABLISHED_ACTION - ) + OngoingCallService.sendCallServiceIntent(applicationContext, OngoingCallService.INCOMING_CALL_ACTION) } } override fun onEstablished(callEstablishedEvent: CallEstablishedEvent?) { - viewModel.updateState { copy(isIncoming = false) } + viewModel.updateState { copy(isEstablished = true) } runOnUiThread { - OngoingCallService.sendCallServiceIntent( - applicationContext, - OngoingCallService.CALL_ESTABLISHED_ACTION - ) + OngoingCallService.sendCallServiceIntent(applicationContext, OngoingCallService.CALL_ESTABLISHED_ACTION) } } override fun onHangup(callHangupEvent: CallHangupEvent?) { - viewModel.updateState { copy(isFinished = true) } + val message = errorMapper.getMessageForError(RtcUiError(callHangupEvent?.errorCode ?: ErrorCode.UNKNOWN)) + viewModel.updateState { copy(isFinished = true, error = message) } } override fun onReconnecting(reconnectingEvent: ReconnectingEvent?) { viewModel.updateState { copy(callAlert = CallAlert.Mode.Reconnecting) } runOnUiThread { - OngoingCallService.sendCallServiceIntent( - applicationContext, - OngoingCallService.CALL_RECONNECTING_ACTION - ) + OngoingCallService.sendCallServiceIntent(applicationContext, OngoingCallService.CALL_RECONNECTING_ACTION) } } override fun onReconnected(reconnectedEvent: ReconnectedEvent?) { viewModel.updateState { copy(callAlert = null) } runOnUiThread { - OngoingCallService.sendCallServiceIntent( - applicationContext, - OngoingCallService.CALL_RECONNECTED_ACTION - ) + OngoingCallService.sendCallServiceIntent(applicationContext, OngoingCallService.CALL_RECONNECTED_ACTION) } } }) diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/CallViewModel.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/CallViewModel.kt similarity index 88% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/CallViewModel.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/CallViewModel.kt index 83f7b782..fde9a523 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/CallViewModel.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/CallViewModel.kt @@ -1,20 +1,20 @@ -package com.infobip.webrtc.ui +package com.infobip.webrtc.ui.internal.ui import android.content.res.Configuration import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.infobip.webrtc.Injector -import com.infobip.webrtc.TAG import com.infobip.webrtc.sdk.api.event.network.NetworkQualityChangedEvent import com.infobip.webrtc.sdk.api.model.CallStatus import com.infobip.webrtc.sdk.api.model.network.NetworkQuality import com.infobip.webrtc.sdk.api.model.video.RTCVideoTrack import com.infobip.webrtc.sdk.api.model.video.ScreenCapturer import com.infobip.webrtc.sdk.impl.event.listener.DefaultNetworkQualityEventListener -import com.infobip.webrtc.ui.listeners.RtcUiCallEventListener -import com.infobip.webrtc.ui.model.CallState -import com.infobip.webrtc.ui.view.CallAlert +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.core.TAG +import com.infobip.webrtc.ui.internal.listener.RtcUiCallEventListener +import com.infobip.webrtc.ui.internal.model.CallState +import com.infobip.webrtc.ui.internal.ui.view.CallAlert import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow @@ -34,7 +34,7 @@ import java.time.format.DateTimeFormatter internal class CallViewModel : ViewModel() { private val _state = MutableStateFlow( CallState( - isIncoming = true, + isEstablished = false, isMuted = false, isPeerMuted = false, elapsedTimeSeconds = 0, @@ -90,23 +90,28 @@ internal class CallViewModel : ViewModel() { fun accept() { callsDelegate.accept() setNetworkQualityListener() - updateState { copy(isIncoming = false) } + updateState { copy(isEstablished = true) } } - fun decline() { + fun decline(error: String = "") { + updateState { copy(isFinished = true, error = error) } callsDelegate.decline() - updateState { copy(isFinished = true) } } - fun endCall() { - if (state.value.isIncoming) { - decline() + fun hangup(error: String = "") { + updateState { copy(isFinished = true, error = error) } + callsDelegate.hangup() + } + + fun endCall(error: String = "") { + if (isEstablished()) { + hangup(error) } else { - hangup() + decline(error) } } - fun onError(message: String) { + fun onError(message: String?) { updateState { copy( error = message, @@ -118,6 +123,10 @@ internal class CallViewModel : ViewModel() { } } + fun cleanError() { + updateState { copy(error = null) } + } + fun formatTime(durationSeconds: Int): String { return timeFormatter.format(LocalTime.ofSecondOfDay(durationSeconds.toLong())) } @@ -142,11 +151,6 @@ internal class CallViewModel : ViewModel() { } } - fun hangup() { - callsDelegate.hangup() - updateState { copy(isFinished = true) } - } - fun toggleVideo() { runCatching { val newValue = !state.value.isLocalVideo @@ -189,8 +193,8 @@ internal class CallViewModel : ViewModel() { return callsDelegate.duration() } - fun isIncomingCall(): Boolean { - return state.value.isIncoming + fun isEstablished(): Boolean { + return state.value.isEstablished } private fun setNetworkQualityListener() { diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/fragments/InCallFragment.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/fragment/InCallFragment.kt similarity index 86% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/fragments/InCallFragment.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/fragment/InCallFragment.kt index 6687501d..70b777d4 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/fragments/InCallFragment.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/fragment/InCallFragment.kt @@ -1,8 +1,9 @@ -package com.infobip.webrtc.ui.fragments +package com.infobip.webrtc.ui.internal.ui.fragment import android.Manifest import android.annotation.SuppressLint import android.app.Activity.RESULT_OK +import android.app.AppOpsManager import android.app.PictureInPictureParams import android.content.BroadcastReceiver import android.content.Context @@ -36,22 +37,22 @@ import androidx.lifecycle.coroutineScope import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.infobip.webrtc.Injector -import com.infobip.webrtc.Injector.colors -import com.infobip.webrtc.TAG import com.infobip.webrtc.sdk.api.model.video.RTCVideoTrack import com.infobip.webrtc.sdk.api.model.video.ScreenCapturer import com.infobip.webrtc.ui.* import com.infobip.webrtc.ui.databinding.FragmentInCallBinding -import com.infobip.webrtc.ui.model.CallState +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.core.TAG +import com.infobip.webrtc.ui.internal.model.CallState +import com.infobip.webrtc.ui.internal.service.ScreenShareService +import com.infobip.webrtc.ui.internal.ui.CallViewModel +import com.infobip.webrtc.ui.internal.ui.view.CircleImageButton +import com.infobip.webrtc.ui.internal.ui.view.InCallButtonAbs +import com.infobip.webrtc.ui.internal.ui.view.PipParamsFactory +import com.infobip.webrtc.ui.internal.ui.view.RowImageButton +import com.infobip.webrtc.ui.internal.utils.px +import com.infobip.webrtc.ui.internal.utils.show import com.infobip.webrtc.ui.model.InCallButton -import com.infobip.webrtc.ui.service.ScreenShareService -import com.infobip.webrtc.ui.utils.px -import com.infobip.webrtc.ui.utils.show -import com.infobip.webrtc.ui.view.CircleImageButton -import com.infobip.webrtc.ui.view.InCallButtonAbs -import com.infobip.webrtc.ui.view.PipParamsFactory -import com.infobip.webrtc.ui.view.RowImageButton import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -124,8 +125,7 @@ class InCallFragment : Fragment() { Log.d(TAG, "Starting screen sharing") viewModel.shareScreen(ScreenCapturer(result.resultCode, result.data)) .onSuccess { - switchLocalTrackSink() - switchRemoteTrackSink() + onScreenShareStateChanged() }.onFailure { ScreenShareService.sendScreenShareServiceIntent( requireContext(), @@ -136,6 +136,11 @@ class InCallFragment : Fragment() { } } } + private val isPipSupported by lazy { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + && requireActivity().packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) + && hasPipPermission() + } private val pipActionsReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == ACTION_PIP) { @@ -252,10 +257,6 @@ class InCallFragment : Fragment() { screenSharingDisable.setOnClickListener { toggleScreenShare() } - val isPipSupported = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && requireActivity().packageManager.hasSystemFeature( - PackageManager.FEATURE_PICTURE_IN_PICTURE - ) if (isPipSupported) { collapseCallButton.setOnClickListener { requireActivity().enterPictureInPictureMode(createPipParams()) } registerReceiver( @@ -265,7 +266,6 @@ class InCallFragment : Fragment() { RECEIVER_NOT_EXPORTED ) } - collapseCallButton.isVisible = isPipSupported } } @@ -285,7 +285,7 @@ class InCallFragment : Fragment() { bottomSheetBehavior?.state = BottomSheetBehavior.STATE_HALF_EXPANDED // main area - Injector.inCallButtons.take(4).forEach { inCallButton -> + Injector.cache.inCallButtons.take(4).forEach { inCallButton -> val button = CircleImageButton(requireContext()).apply { id = inCallButton.id setIcon(inCallButton.iconRes) @@ -295,7 +295,7 @@ class InCallFragment : Fragment() { initBottomSheetButton(inCallButton, button) } // dragged area - Injector.inCallButtons.asSequence().drop(4).forEach { inCallButton -> + Injector.cache.inCallButtons.asSequence().drop(4).forEach { inCallButton -> val button = RowImageButton(requireContext()).apply { id = inCallButton.id setIcon(inCallButton.iconRes) @@ -391,7 +391,7 @@ class InCallFragment : Fragment() { R.id.rtc_ui_hang_up_button -> { hangupButton = button - colors?.let { res -> + Injector.cache.colors?.let { res -> hangupButton?.setIconTint(ColorStateList.valueOf(res.rtcUiActionsIcon)) hangupButton?.setBackgroundColor(ColorStateList.valueOf(res.rtcUiHangup)) } @@ -436,15 +436,16 @@ class InCallFragment : Fragment() { binding.videoGroup.isVisible = (isRemoteVideo || isLocalScreenShare || isRemoteScreenShare) && !isPip && showControls binding.elapsedTimeVoice.isVisible = (!isRemoteVideo && !isRemoteScreenShare && !isLocalScreenShare) || isPip //screenshare - binding.screenSharingGroup.isVisible = isLocalScreenShare - colors.let { res -> (if (isLocalScreenShare) res?.rtcUiActionsBackground else res?.rtcUiBackground)?.let { binding.background.setBackgroundColor(it) } } + binding.screenSharingNotice.isVisible = isLocalScreenShare + binding.screenSharingDisable.isVisible = isLocalScreenShare && !isPip + Injector.cache.colors.let { res -> (if (isLocalScreenShare) res?.rtcUiActionsBackground else res?.rtcUiBackground)?.let { binding.background.setBackgroundColor(it) } } //another views binding.connectionAlert.isVisible = callAlert != null && !isPip && showControls binding.connectionAlert.setMode(callAlert) binding.mutedMicrophoneAlert.isVisible = isMuted && !isPip && showControls binding.peerMuteIndicatorInVideo.isVisible = (isRemoteVideo || isLocalScreenShare || isRemoteScreenShare) && isPeerMuted == true && !isPip && showControls binding.peerMuteIndicatorInVoice.isVisible = !isRemoteVideo && !isLocalScreenShare && !isRemoteScreenShare && isPeerMuted == true && !isPip - binding.collapseCallButton.isVisible = !isPip && showControls + binding.collapseCallButton.isVisible = !isPip && showControls && isPipSupported //in PIP binding.nameInPip.isVisible = isPip //buttons @@ -496,13 +497,22 @@ class InCallFragment : Fragment() { private fun handleScreenShareTrack(track: RTCVideoTrack?) { runCatching { track?.addSink(binding.remoteVideo) - switchLocalTrackSink() - switchRemoteTrackSink() + onScreenShareStateChanged() }.onFailure { Log.e(TAG, "Handle remote video sink failed.", it) } } + /** + * We have 2 separate renderers for local/video video. One is used when (local) screen sharing + * is disabled and second when enabled. This function ensures correct rendered is used when screen + * sharing is enabled/disabled. + */ + private fun onScreenShareStateChanged() { + switchLocalTrackSink() + switchRemoteTrackSink() + } + private fun switchLocalTrackSink() { runCatching { viewModel.state.value.localVideoTrack?.run { @@ -546,8 +556,7 @@ class InCallFragment : Fragment() { requireContext(), ScreenShareService.ACTION_STOP_SCREEN_SHARE ) - switchLocalTrackSink() - switchRemoteTrackSink() + onScreenShareStateChanged() } else { bottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED val mediaProjectionManager = @@ -567,7 +576,7 @@ class InCallFragment : Fragment() { } private fun customize() { - colors?.let { res -> + Injector.cache.colors?.let { res -> val foregroundColorStateList = ColorStateList.valueOf(res.rtcUiForeground) with(binding) { toolbarBackground.setBackgroundColor(res.rtcUiOverlayBackground) @@ -650,27 +659,56 @@ class InCallFragment : Fragment() { } } + private fun hasPipPermission(): Boolean { + val context = requireContext() + val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as? AppOpsManager? + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val mode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + appOps?.unsafeCheckOpNoThrow( + AppOpsManager.OPSTR_PICTURE_IN_PICTURE, + android.os.Process.myUid(), + context.packageName + ) + } else { + @Suppress("DEPRECATION") + appOps?.checkOpNoThrow( + AppOpsManager.OPSTR_PICTURE_IN_PICTURE, + android.os.Process.myUid(), + context.packageName + ) + } + mode == AppOpsManager.MODE_ALLOWED + } else { + false + } + } + override fun onDestroyView() { - with(binding) { - localVideo.release() - remoteVideo.release() - localVideoScreenSharing.release() - remoteVideoScreenSharing.release() - root.setOnClickListener(null) - remoteVideo.setOnClickListener(null) - collapseCallButton.setOnClickListener(null) - localVideo.setOnTouchListener(null) - screenSharingRenderers.setOnTouchListener(null) + runCatching { + with(binding) { + localVideo.release() + remoteVideo.release() + localVideoScreenSharing.release() + remoteVideoScreenSharing.release() + root.setOnClickListener(null) + remoteVideo.setOnClickListener(null) + collapseCallButton.setOnClickListener(null) + localVideo.setOnTouchListener(null) + screenSharingRenderers.setOnTouchListener(null) + } + _binding = null + hangupButton?.setOnClickListener(null) + muteButton?.setOnClickListener(null) + speakerButton?.setOnClickListener(null) + flipCamButton?.setOnClickListener(null) + videoButton?.setOnClickListener(null) + screenShareButton?.setOnClickListener(null) + customInCallButtons.forEach { it.setOnClickListener(null) } + if (isPipSupported) + context?.unregisterReceiver(pipActionsReceiver) + }.onFailure { + Log.e(TAG, "Cleanup failed.", it) } - _binding = null - hangupButton?.setOnClickListener(null) - muteButton?.setOnClickListener(null) - speakerButton?.setOnClickListener(null) - flipCamButton?.setOnClickListener(null) - videoButton?.setOnClickListener(null) - screenShareButton?.setOnClickListener(null) - customInCallButtons.forEach { it.setOnClickListener(null) } - context?.unregisterReceiver(pipActionsReceiver) super.onDestroyView() } } \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/fragments/IncomingCallFragment.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/fragment/IncomingCallFragment.kt similarity index 88% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/fragments/IncomingCallFragment.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/fragment/IncomingCallFragment.kt index 2156a149..cb78739a 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/fragments/IncomingCallFragment.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/fragment/IncomingCallFragment.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.fragments +package com.infobip.webrtc.ui.internal.ui.fragment import android.content.res.ColorStateList import android.os.Bundle @@ -9,13 +9,11 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.core.widget.TextViewCompat import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import com.infobip.webrtc.Injector.colors -import com.infobip.webrtc.Injector.icons -import com.infobip.webrtc.Injector.incomingCallMessageStyle -import com.infobip.webrtc.ui.CallViewModel import com.infobip.webrtc.ui.R import com.infobip.webrtc.ui.databinding.FragmentIncomingCallBinding -import com.infobip.webrtc.ui.utils.navigate +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.ui.CallViewModel +import com.infobip.webrtc.ui.internal.utils.navigate class IncomingCallFragment : Fragment() { private var _binding: FragmentIncomingCallBinding? = null @@ -37,7 +35,6 @@ class IncomingCallFragment : Fragment() { } decline.setOnClickListener { viewModel.decline() - viewModel.updateState { copy(isFinished = true) } } } } @@ -51,12 +48,12 @@ class IncomingCallFragment : Fragment() { private fun customize() { with(binding) { - icons?.let { res -> + Injector.cache.icons?.let { res -> logo.setImageResource(res.callsIcon) accept.setIcon(res.accept) decline.setIcon(res.decline) } - colors?.let { res -> + Injector.cache.colors?.let { res -> val foregroundColorStateList = ColorStateList.valueOf(res.rtcUiForeground) logo.imageTintList = foregroundColorStateList name.setTextColor(foregroundColorStateList) @@ -66,7 +63,7 @@ class IncomingCallFragment : Fragment() { accept.setBackgroundColor(ColorStateList.valueOf(res.rtcUiAccept)) background.setBackgroundColor(res.rtcUiBackground) } - incomingCallMessageStyle?.let { res -> + Injector.cache.incomingCallMessageStyle?.let { res -> res.headlineText?.takeIf { it.isNotEmpty() }.let { customHeadline.text = it customHeadline.visibility = View.VISIBLE diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/CallAlert.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/CallAlert.kt similarity index 90% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/CallAlert.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/CallAlert.kt index 7f3a50d8..2749e4cc 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/CallAlert.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/CallAlert.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.view +package com.infobip.webrtc.ui.internal.ui.view import android.content.Context import android.content.res.ColorStateList @@ -7,11 +7,11 @@ import android.view.LayoutInflater import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.constraintlayout.widget.ConstraintLayout -import com.infobip.webrtc.Injector import com.infobip.webrtc.ui.R import com.infobip.webrtc.ui.databinding.WidgetCallAlertBinding +import com.infobip.webrtc.ui.internal.core.Injector -class CallAlert @JvmOverloads constructor( +internal class CallAlert @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, @@ -22,7 +22,7 @@ class CallAlert @JvmOverloads constructor( init { binding.run { - Injector.colors?.let { + Injector.cache.colors?.let { setBackgroundColor(it.rtcUiAlertBackground) alertText.setTextColor(it.rtcUiAlertText) } diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/CircleImageButton.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/CircleImageButton.kt similarity index 93% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/CircleImageButton.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/CircleImageButton.kt index 3871b4ba..46d48d15 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/CircleImageButton.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/CircleImageButton.kt @@ -1,14 +1,14 @@ -package com.infobip.webrtc.ui.view +package com.infobip.webrtc.ui.internal.ui.view import android.content.Context import android.content.res.ColorStateList import android.util.AttributeSet import android.view.LayoutInflater import androidx.annotation.DrawableRes -import com.infobip.webrtc.Injector import com.infobip.webrtc.ui.R import com.infobip.webrtc.ui.databinding.WidgetCircleImageButtonBinding -import com.infobip.webrtc.ui.utils.activatedColorStateList +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.utils.activatedColorStateList internal class CircleImageButton @JvmOverloads constructor( context: Context, @@ -19,7 +19,7 @@ internal class CircleImageButton @JvmOverloads constructor( private val binding: WidgetCircleImageButtonBinding private val actionsBackgroundColorStateList by lazy { - Injector.colors?.let { + Injector.cache.colors?.let { activatedColorStateList( it.rtcUiActionsBackgroundChecked, it.rtcUiActionsBackground @@ -27,7 +27,7 @@ internal class CircleImageButton @JvmOverloads constructor( } } private val actionsIconColorStateList by lazy { - Injector.colors?.let { + Injector.cache.colors?.let { activatedColorStateList( it.rtcUiActionsIconChecked, it.rtcUiActionsIcon diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/InCallButtonAbs.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/InCallButtonAbs.kt similarity index 94% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/InCallButtonAbs.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/InCallButtonAbs.kt index 9fc25991..282f23e6 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/InCallButtonAbs.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/InCallButtonAbs.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.view +package com.infobip.webrtc.ui.internal.ui.view import android.content.Context import android.content.res.ColorStateList @@ -12,9 +12,9 @@ import androidx.annotation.DrawableRes import androidx.annotation.StyleableRes import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat -import com.infobip.webrtc.ui.utils.activatedColorStateList +import com.infobip.webrtc.ui.internal.utils.activatedColorStateList -abstract class InCallButtonAbs( +internal abstract class InCallButtonAbs( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/PipParamsFactory.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/PipParamsFactory.kt similarity index 90% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/PipParamsFactory.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/PipParamsFactory.kt index 70d0c1af..72551935 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/PipParamsFactory.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/PipParamsFactory.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.view +package com.infobip.webrtc.ui.internal.ui.view import android.app.PendingIntent import android.app.PictureInPictureParams @@ -9,13 +9,13 @@ import android.os.Build import android.util.Rational import androidx.annotation.RequiresApi import com.infobip.webrtc.ui.R -import com.infobip.webrtc.ui.fragments.InCallFragment.Companion.PIP_ACTION_HANGUP -import com.infobip.webrtc.ui.fragments.InCallFragment.Companion.PIP_ACTION_MUTE -import com.infobip.webrtc.ui.fragments.InCallFragment.Companion.PIP_ACTION_SPEAKER -import com.infobip.webrtc.ui.fragments.InCallFragment.Companion.PIP_ACTION_VIDEO -import com.infobip.webrtc.ui.fragments.InCallFragment.Companion.pipActionIntent +import com.infobip.webrtc.ui.internal.ui.fragment.InCallFragment.Companion.PIP_ACTION_HANGUP +import com.infobip.webrtc.ui.internal.ui.fragment.InCallFragment.Companion.PIP_ACTION_MUTE +import com.infobip.webrtc.ui.internal.ui.fragment.InCallFragment.Companion.PIP_ACTION_SPEAKER +import com.infobip.webrtc.ui.internal.ui.fragment.InCallFragment.Companion.PIP_ACTION_VIDEO +import com.infobip.webrtc.ui.internal.ui.fragment.InCallFragment.Companion.pipActionIntent -object PipParamsFactory { +internal object PipParamsFactory { private const val REQUEST_MUTE = 1 private const val REQUEST_UNMUTE = 2 diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/RowImageButton.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/RowImageButton.kt similarity index 84% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/RowImageButton.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/RowImageButton.kt index 8efefadd..5428ed3b 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/RowImageButton.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/ui/view/RowImageButton.kt @@ -1,4 +1,4 @@ -package com.infobip.webrtc.ui.view +package com.infobip.webrtc.ui.internal.ui.view import android.content.Context import android.content.res.ColorStateList @@ -6,9 +6,9 @@ import android.util.AttributeSet import android.view.LayoutInflater import androidx.annotation.DrawableRes import androidx.core.content.ContextCompat -import com.infobip.webrtc.Injector import com.infobip.webrtc.ui.databinding.RowImageButtonBinding -import com.infobip.webrtc.ui.utils.hide +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.utils.hide internal class RowImageButton @JvmOverloads constructor( context: Context, @@ -37,7 +37,7 @@ internal class RowImageButton @JvmOverloads constructor( override fun setEnabled(enabled: Boolean) { super.setEnabled(enabled) binding.clicker.isEnabled = enabled - val labelColor = if (enabled) Injector.colors?.rtcUiForeground else Injector.colors?.rtcUiTextSecondary + val labelColor = if (enabled) Injector.cache.colors?.rtcUiForeground else Injector.cache.colors?.rtcUiTextSecondary labelColor?.let { binding.label.setTextColor(it) } } @@ -57,21 +57,21 @@ internal class RowImageButton @JvmOverloads constructor( } override fun setIconTint(colorStateList: ColorStateList?) { - Injector.colors?.rtcUiColorActionsRowIcon?.let { customizedColor -> + Injector.cache.colors?.rtcUiColorActionsRowIcon?.let { customizedColor -> val color = colorStateList?.getColorForState(intArrayOf(-android.R.attr.state_activated), customizedColor) ?: customizedColor binding.icon.imageTintList = ColorStateList.valueOf(color) } } override fun setBackgroundColor(colorStateList: ColorStateList?) { - Injector.colors?.rtcUiColorActionsRowBackground?.let { customizedColor -> + Injector.cache.colors?.rtcUiColorActionsRowBackground?.let { customizedColor -> val color = colorStateList?.getColorForState(intArrayOf(-android.R.attr.state_activated), customizedColor) ?: customizedColor binding.clicker.setCardBackgroundColor(color) } } override fun setLabelColor(color: Int?) { - Injector.colors?.let { customized -> + Injector.cache.colors?.let { customized -> val textColor = color?.let { ContextCompat.getColor(context, color) } ?: customized.rtcUiColorActionsRowLabel binding.label.setTextColor(textColor) } diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/utils/Extensions.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/utils/Extensions.kt similarity index 82% rename from infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/utils/Extensions.kt rename to infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/utils/Extensions.kt index 54ef2601..f874d9ed 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/utils/Extensions.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/internal/utils/Extensions.kt @@ -1,8 +1,7 @@ -package com.infobip.webrtc.ui.utils +package com.infobip.webrtc.ui.internal.utils //noinspection SuspiciousImport import android.R -import android.app.Service import android.content.Context import android.content.res.ColorStateList import android.content.res.Configuration @@ -20,6 +19,8 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.commit +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow import java.util.Locale @@ -28,7 +29,7 @@ import java.util.Locale * * @property show true for VISIBLE, false for GONE */ -fun View.show(show: Boolean = true) { +internal fun View.show(show: Boolean = true) { this.visibility = if (show) View.VISIBLE else View.GONE } @@ -37,7 +38,7 @@ fun View.show(show: Boolean = true) { * * @property hide true for GONE, false for VISIBLE */ -fun View.hide(hide: Boolean = true) { +internal fun View.hide(hide: Boolean = true) { show(!hide) } @@ -46,7 +47,7 @@ fun View.hide(hide: Boolean = true) { * * @property invisible true for INVISIBLE, false for VISIBLE */ -fun View.invisible(invisible: Boolean = true) { +internal fun View.invisible(invisible: Boolean = true) { this.visibility = if (invisible) View.INVISIBLE else View.VISIBLE } @@ -55,7 +56,7 @@ fun View.invisible(invisible: Boolean = true) { * * @property visible true for VISIBLE, false for INVISIBLE */ -fun View.visible(visible: Boolean = true) { +internal fun View.visible(visible: Boolean = true) { invisible(!visible) } @@ -99,15 +100,6 @@ internal fun FragmentManager.navigate(destination: Fragment, @IdRes containerId: } } -@Suppress("DEPRECATION") -internal fun Service.stopForegroundRemove() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - stopForeground(Service.STOP_FOREGROUND_REMOVE) - } else { - stopForeground(true) - } -} - internal fun T.applyIf(condition: T.() -> Boolean, block: T.() -> Unit): T { if (condition()) { block() @@ -173,4 +165,21 @@ internal fun Context.resolveStyledStringAttribute( } @ColorInt -fun Context.getColorCompat(@ColorRes id: Int): Int = ContextCompat.getColor(this, id) \ No newline at end of file +internal fun Context.getColorCompat(@ColorRes id: Int): Int = ContextCompat.getColor(this, id) + +internal fun Flow.throttleFirst(windowDuration: Long): Flow = flow { + var windowStartTime = System.currentTimeMillis() + var emitted = false + collect { value -> + val currentTime = System.currentTimeMillis() + val delta = currentTime - windowStartTime + if (delta >= windowDuration) { + windowStartTime += delta / windowDuration * windowDuration + emitted = false + } + if (!emitted) { + emit(value) + emitted = true + } + } +} \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/InCallButton.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/InCallButton.kt index b27d7d16..817e4a59 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/InCallButton.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/InCallButton.kt @@ -3,8 +3,8 @@ package com.infobip.webrtc.ui.model import android.view.View import androidx.annotation.DrawableRes import androidx.annotation.StringRes -import com.infobip.webrtc.Injector import com.infobip.webrtc.ui.R +import com.infobip.webrtc.ui.internal.core.Injector sealed class InCallButton( @StringRes open val labelRes: Int, @@ -16,7 +16,7 @@ sealed class InCallButton( internal object HangUp : InCallButton( R.string.mm_hangup, - Injector.icons?.endCall ?: R.drawable.ic_endcall, + Injector.cache.icons?.endCall ?: R.drawable.ic_endcall, null ) { override val id: Int = R.id.rtc_ui_hang_up_button @@ -24,39 +24,39 @@ sealed class InCallButton( data class Mute(override var onClick: () -> Unit = {}) : InCallButton( R.string.mm_microphone, - Injector.icons?.mute ?: R.drawable.ic_unmute, - Injector.icons?.unMute ?: R.drawable.ic_mute, + Injector.cache.icons?.mute ?: R.drawable.ic_unmute, + Injector.cache.icons?.unMute ?: R.drawable.ic_mute, ) { override val id: Int = R.id.rtc_ui_mute_button } data class Speaker (override var onClick: () -> Unit = {}) : InCallButton( R.string.mm_speaker, - Injector.icons?.speakerOff ?: R.drawable.ic_speaker_off, - Injector.icons?.speaker ?: R.drawable.ic_speaker, + Injector.cache.icons?.speakerOff ?: R.drawable.ic_speaker_off, + Injector.cache.icons?.speaker ?: R.drawable.ic_speaker, ) { override val id: Int = R.id.rtc_ui_speaker_button } data class FlipCam (override var onClick: () -> Unit = {}) : InCallButton( R.string.mm_flip_camera, - Injector.icons?.flipCamera ?: R.drawable.ic_flip_camera + Injector.cache.icons?.flipCamera ?: R.drawable.ic_flip_camera ) { override val id: Int = R.id.rtc_ui_flip_cam_button } data class ScreenShare (override var onClick: () -> Unit = {}) : InCallButton( R.string.mm_screen_share, - Injector.icons?.screenShare ?: R.drawable.ic_screen_share, - Injector.icons?.screenShareOff ?: R.drawable.ic_screen_share_off, + Injector.cache.icons?.screenShare ?: R.drawable.ic_screen_share, + Injector.cache.icons?.screenShareOff ?: R.drawable.ic_screen_share_off, ) { override val id: Int = R.id.rtc_ui_screen_share_button } data class Video (override var onClick: () -> Unit = {}) : InCallButton( R.string.mm_video_call, - Injector.icons?.videoOff ?: R.drawable.ic_video_off, - Injector.icons?.video ?: R.drawable.ic_video, + Injector.cache.icons?.videoOff ?: R.drawable.ic_video_off, + Injector.cache.icons?.video ?: R.drawable.ic_video, ) { override val id: Int = R.id.rtc_ui_video_button } diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiError.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiError.kt new file mode 100644 index 00000000..67b2646f --- /dev/null +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/model/RtcUiError.kt @@ -0,0 +1,24 @@ +package com.infobip.webrtc.ui.model + +import com.infobip.webrtc.sdk.api.model.ErrorCode + +data class RtcUiError( + val id: Int, + val name: String, + val description: String? +) { + + constructor(errorCode: ErrorCode) : this(errorCode.id, errorCode.name, errorCode.description) + + companion object { + @JvmField + val MISSING_READ_PHONE_STATE_PERMISSION = RtcUiError(20_000, "MISSING_READ_PHONE_STATE_PERMISSION" , "Phone state permission not granted.") + @JvmField + val MISSING_POST_NOTIFICATIONS_PERMISSION = RtcUiError(20_001, "MISSING_POST_NOTIFICATIONS_PERMISSION" , "Post notifications permission not granted.") + @JvmField + val INCOMING_WEBRTC_CALL_WHILE_CELLULAR_CALL = RtcUiError(20_002, "INCOMING_WEBRTC_CALL_WHILE_CELLULAR_CALL" , "Incoming WebRTC call appeared while there is ringing/ongoing cellular call.") + @JvmField + val CELLULAR_CALL_ACCEPTED_WHILE_WEBRTC_CALL = RtcUiError(20_003, "CELLULAR_CALL_ACCEPTED_WHILE_WEBRTC_CALL" , "Cellular call accepted during ongoing WebRTC call.") + } +} + diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/BaseService.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/BaseService.kt deleted file mode 100644 index 6a878958..00000000 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/BaseService.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.infobip.webrtc.ui.service - -import android.app.Service -import android.content.Context -import com.infobip.webrtc.Injector -import com.infobip.webrtc.ui.utils.applyLocale - -abstract class BaseService: Service() { - - override fun attachBaseContext(newBase: Context?) { - val newContext = Injector.locale?.let { newBase?.applyLocale(it) } ?: newBase - super.attachBaseContext(newContext) - } - -} \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/IncomingCallService.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/IncomingCallService.kt index 9ded84b2..38652b71 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/IncomingCallService.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/service/IncomingCallService.kt @@ -3,9 +3,9 @@ package com.infobip.webrtc.ui.service import android.util.Log import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage -import com.infobip.webrtc.Injector -import com.infobip.webrtc.TAG -import com.infobip.webrtc.ui.delegate.CallsDelegate +import com.infobip.webrtc.ui.internal.core.Injector +import com.infobip.webrtc.ui.internal.core.TAG +import com.infobip.webrtc.ui.internal.delegate.CallsDelegate import org.infobip.mobile.messaging.cloud.firebase.MobileMessagingFirebaseService abstract class IncomingCallService : FirebaseMessagingService() { diff --git a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/styles/IncomingCallMessageStyle.kt b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/styles/IncomingCallMessageStyle.kt index 6c5554cb..3d460d1d 100644 --- a/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/styles/IncomingCallMessageStyle.kt +++ b/infobip-rtc-ui/src/main/java/com/infobip/webrtc/ui/view/styles/IncomingCallMessageStyle.kt @@ -7,7 +7,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StyleRes import androidx.core.content.ContextCompat import com.infobip.webrtc.ui.R -import com.infobip.webrtc.ui.utils.resolveString +import com.infobip.webrtc.ui.internal.utils.resolveString data class IncomingCallMessageStyle( val headlineText: String? = null, diff --git a/infobip-rtc-ui/src/main/res/layout/fragment_in_call.xml b/infobip-rtc-ui/src/main/res/layout/fragment_in_call.xml index afdafe55..c5e53cc1 100644 --- a/infobip-rtc-ui/src/main/res/layout/fragment_in_call.xml +++ b/infobip-rtc-ui/src/main/res/layout/fragment_in_call.xml @@ -180,6 +180,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> + + - - - - - - مطلوب إذن الصوت للتعامل مع المكالمات. + مطلوب إذن اتصال Bluetooth لاستخدام جهازك المتصل بتقنية Bluetooth لإجراء المكالمات. + مطلوب إذن حالة الهاتف لتجنب WebRTC والمكالمات الخلوية الموازية. انتهت المكالمة تتسبب شبكتك في ضعف جودة الاتصال المكالمات @@ -13,6 +15,9 @@ صامت كتم الصوت إذن الإعلام مفقود. رفض المكالمة الواردة. + إذن حالة الهاتف مفقود. رفض المكالمة الواردة. + هناك مكالمة خلوية جارية. رفض المكالمة الواردة. + هناك مكالمة خلوية جارية. انتهت المكالمة. حسنا تسجيل المكالمات تسجيل المكالمات قيد التقدم diff --git a/infobip-rtc-ui/src/main/res/values-b+zh+Hans/strings.xml b/infobip-rtc-ui/src/main/res/values-b+zh+Hans/strings.xml index d344a373..f625d8c9 100644 --- a/infobip-rtc-ui/src/main/res/values-b+zh+Hans/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-b+zh+Hans/strings.xml @@ -1,6 +1,7 @@ - 需要音频权限来处理通话 + 需要蓝牙连接权限才能使用蓝牙连接设备进行通话。 + 需要电话状态许可才能避免并行 WebRTC 和蜂窝呼叫。 通话结束 您的网络质量差 通话 @@ -14,6 +15,9 @@ 闭麦 关闭麦克风 缺少通知权限。拒绝来电。 + 缺少电话状态许可。拒绝来电。 + 手机通话正在进行中。拒绝来电。 + 手机通话正在进行中。通话完毕。 好的 致电登记 来电登记正在进行中 @@ -31,4 +35,4 @@ 您正在共享一个屏幕。 停止共享屏幕 通话被挂断。正在尝试重新连接... - + \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/res/values-b+zh+Hant/strings.xml b/infobip-rtc-ui/src/main/res/values-b+zh+Hant/strings.xml index c5d21459..b5e6c124 100644 --- a/infobip-rtc-ui/src/main/res/values-b+zh+Hant/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-b+zh+Hant/strings.xml @@ -1,13 +1,11 @@ - 處理呼叫需要音頻權限。 + 需要藍牙連線權限才能使用藍牙連接裝置進行通話。 + 需要電話狀態許可才能避免並行 WebRTC 和蜂窩呼叫。 通話結束 - 來電登記 - 來電登記正在進行中 互聯網連接較弱 來電 取消 - 通話被掛斷。正在嘗試重新連線… 翻轉相機 掛斷 通話中 @@ -17,18 +15,24 @@ 靜音 沉默的 缺少通知權限。 拒絕來電。 + 缺少電話狀態許可。拒絕來電。 + 手機通話正在進行中。拒絕來電。 + 手機通話正在進行中。通話完畢。 + 來電登記 + 來電登記正在進行中 屏幕共享 屏幕共享 您的屏幕已共享 喇叭 - 停止共享螢幕 未知 未知錯誤 取消靜音 視頻 視訊通話 視頻通話需要攝像頭權限。 - 您正在共享一個螢幕。 您的麥克風已靜音 - + 您正在共享一個螢幕。 + 停止共享螢幕 + 通話被掛斷。正在嘗試重新連線… + \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/res/values-cs/strings.xml b/infobip-rtc-ui/src/main/res/values-cs/strings.xml index 606f62f7..03af8fef 100644 --- a/infobip-rtc-ui/src/main/res/values-cs/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-cs/strings.xml @@ -1,5 +1,7 @@ Ke zpracování hovorů je vyžadováno oprávnění ke zvuku. + K používání zařízení připojeného přes Bluetooth pro hovory je vyžadováno oprávnění k připojení Bluetooth. + Aby se zabránilo paralelním WebRTC a mobilním hovorům, je vyžadováno povolení stavu telefonu. Hovor ukončen Vaše síť způsobuje špatnou kvalitu hovoru Hovory @@ -13,6 +15,9 @@ Ztlumeno Ztlumit zvuk Chybí oprávnění k oznámení. Odmítam příchozí hovor. + Chybí povolení stavu telefonu. Odmítání příchozího hovoru. + Probíhá mobilní hovor. Odmítání příchozího hovoru. + Probíhá mobilní hovor. Hovor ukončen. OK Registrace hovoru Probíhá registrace hovoru diff --git a/infobip-rtc-ui/src/main/res/values-da/strings.xml b/infobip-rtc-ui/src/main/res/values-da/strings.xml index 2ada000f..50d5ac51 100644 --- a/infobip-rtc-ui/src/main/res/values-da/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-da/strings.xml @@ -1,5 +1,7 @@ Lydtilladelse er påkrævet for at håndtere opkald. + Bluetooth-forbindelsestilladelse er påkrævet for at bruge din Bluetooth-forbundne enhed til opkald. + Telefontilstandstilladelse er påkrævet for at undgå parallelle WebRTC- og mobilopkald. Opkald afsluttet Dit netværk forårsager dårlig opkaldskvalitet Opkald @@ -13,6 +15,9 @@ Slået fra Stum Manglende notifikationstilladelse. Afviser indgående opkald. + Manglende tilladelse til telefontilstand. Afviser indgående opkald. + Der er igangværende mobilopkald. Afviser indgående opkald. + Der er igangværende mobilopkald. Opkald afsluttet. Okay Opkaldsregistrering Opkaldsregistrering er i gang diff --git a/infobip-rtc-ui/src/main/res/values-de/strings.xml b/infobip-rtc-ui/src/main/res/values-de/strings.xml index 1a8fd172..650362ec 100644 --- a/infobip-rtc-ui/src/main/res/values-de/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-de/strings.xml @@ -1,5 +1,7 @@ Zur Bearbeitung von Anrufen ist eine Audioberechtigung erforderlich. + Um Ihr über Bluetooth verbundenes Gerät für Anrufe verwenden zu können, ist eine Bluetooth-Verbindungsberechtigung erforderlich. + Um parallele WebRTC- und Mobilfunkanrufe zu vermeiden, ist eine Telefonstatusberechtigung erforderlich. Anruf beendet Ihr Netzwerk verursacht eine schlechte Anrufqualität Anrufe @@ -13,6 +15,9 @@ Stummgeschaltet Stumm Fehlende Benachrichtigungsberechtigung. Ablehnender eingehender Anruf. + Fehlende Berechtigung für den Telefonstatus. Ablehnender eingehender Anruf. + Es gibt einen laufenden Mobilfunkanruf. Ablehnender eingehender Anruf. + Es gibt einen laufenden Mobilfunkanruf. Anruf beendet. OK Anrufregistrierung Die Anrufregistrierung ist im Gange diff --git a/infobip-rtc-ui/src/main/res/values-es/strings.xml b/infobip-rtc-ui/src/main/res/values-es/strings.xml index 155ce20b..0725e881 100644 --- a/infobip-rtc-ui/src/main/res/values-es/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-es/strings.xml @@ -1,5 +1,7 @@ Se requiere permiso de audio para manejar llamadas. + Se requiere permiso de conexión Bluetooth para usar su dispositivo conectado Bluetooth para llamadas. + Se requiere permiso del estado del teléfono para evitar llamadas WebRTC y celulares paralelas. Llamada finalizada Su red está provocando una calidad de llamada deficiente Llamadas @@ -13,6 +15,9 @@ Apagado Silencio Permiso de notificación faltante. Rechazar llamada entrante. + Falta el permiso estatal del teléfono. Rechazando llamada entrante. + Hay una llamada celular en curso. Rechazando llamada entrante. + Hay una llamada celular en curso. Llamada finalizada. OK Registro de llamadas El registro de llamadas está en curso. diff --git a/infobip-rtc-ui/src/main/res/values-fi-rFI/strings.xml b/infobip-rtc-ui/src/main/res/values-fi/strings.xml similarity index 72% rename from infobip-rtc-ui/src/main/res/values-fi-rFI/strings.xml rename to infobip-rtc-ui/src/main/res/values-fi/strings.xml index 90327bcf..e69d2871 100644 --- a/infobip-rtc-ui/src/main/res/values-fi-rFI/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-fi/strings.xml @@ -1,14 +1,13 @@ - Puheluiden käsittelyyn tarvitaan äänilupa. + Bluetooth-yhteyden käyttöoikeus vaaditaan, jotta voit käyttää Bluetooth-yhteyttä laitettasi puheluihin. + Puhelintilan lupa vaaditaan, jotta vältetään rinnakkaiset WebRTC- ja matkapuhelinpuhelut. Puhelu päättynyt - Soiton rekisteröinti - Ilmoittautuminen puheluun on käynnissä Verkkosi aiheuttaa huonon puhelun laadun Puhelut Peruuttaa Käännä kamera - Lopettaa puhelu + Odota Puhelussa Puhelussa Saapuva puhelu @@ -16,7 +15,12 @@ Mykistetty Mykistä Ilmoituslupa puuttuu. Tulevan puhelun hylkääminen. + Puhelimen tilan lupa puuttuu. Tulevan puhelun hylkääminen. + Matkapuhelinpuhelu on meneillään. Tulevan puhelun hylkääminen. + Matkapuhelinpuhelu on meneillään. Puhelu päättynyt. Ok + Soiton rekisteröinti + Ilmoittautuminen puheluun on käynnissä Näytön jako Näytön jakaminen Näyttösi on jaettu @@ -31,4 +35,4 @@ Olet jakamassa näytön. Lopeta näytön jakaminen Puhelu katkesi. Yritetään yhdistää uudelleen… - + \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/res/values-fr/strings.xml b/infobip-rtc-ui/src/main/res/values-fr/strings.xml index 8dbd3821..28d2b63d 100644 --- a/infobip-rtc-ui/src/main/res/values-fr/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-fr/strings.xml @@ -1,5 +1,7 @@ Une autorisation audio est requise pour gérer les appels. + L\'autorisation de connexion Bluetooth est requise pour utiliser votre appareil connecté Bluetooth pour les appels. + L\'autorisation de l\'état du téléphone est requise pour éviter les appels WebRTC et cellulaires parallèles. Appel terminé Votre réseau cause une mauvaise qualité d\'appel Appels @@ -13,6 +15,9 @@ En sourdine Muet Autorisation de notification manquante. Refuser l\'appel entrant. + Autorisation d\'état du téléphone manquante. Appel entrant refusé. + Un appel cellulaire est en cours. Appel entrant refusé. + Un appel cellulaire est en cours. Appel terminé. D\'accord Enregistrement d\'appel L\'enregistrement de l\'appel est en cours diff --git a/infobip-rtc-ui/src/main/res/values-hi/strings.xml b/infobip-rtc-ui/src/main/res/values-hi/strings.xml index 46bb49d5..fecda0bd 100644 --- a/infobip-rtc-ui/src/main/res/values-hi/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-hi/strings.xml @@ -1,5 +1,7 @@ कॉल को संभालने के लिए ऑडियो अनुमति की आवश्यकता होती है। + कॉल के लिए आपके ब्लूटूथ कनेक्टेड डिवाइस का उपयोग करने के लिए ब्लूटूथ कनेक्ट अनुमति आवश्यक है। + समानांतर WebRTC और सेल्युलर कॉल से बचने के लिए फ़ोन स्थिति की अनुमति आवश्यक है। कॉल समाप्त आपका नेटवर्क खराब कॉल गुणवत्ता का कारण बन रहा है कॉल @@ -13,6 +15,9 @@ म्यूट किए गए आवाज़ बंद करना सूचना अनुमति गुम है। आने वाली कॉल को अस्वीकार करना। + फ़ोन स्थिति की अनुमति गुम है. इनकमिंग कॉल कम हो रही है. + सेल्युलर कॉल चल रही है. इनकमिंग कॉल कम हो रही है. + सेल्युलर कॉल चल रही है. कॉल ख़त्म. ठीक कॉल पंजीकरण कॉल पंजीकरण प्रगति पर है diff --git a/infobip-rtc-ui/src/main/res/values-hr/strings.xml b/infobip-rtc-ui/src/main/res/values-hr/strings.xml index 2540caac..d5f67353 100644 --- a/infobip-rtc-ui/src/main/res/values-hr/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-hr/strings.xml @@ -1,5 +1,7 @@ Za upravljanje pozivima potrebno je audiodopuštenje. + Za korištenje vašeg Bluetooth povezanog uređaja za pozive potrebno je dopuštenje za Bluetooth povezivanje. + Potrebno je dopuštenje za stanje telefona kako bi se izbjegli paralelni WebRTC i mobilni pozivi. Poziv završen Vaša mreža uzrokuje lošu kvalitetu poziva Pozivi @@ -13,6 +15,9 @@ prigušen utišati Nedostaje dozvola za obavijesti. Odbijanje dolaznog poziva. + Nedostaje dopuštenje za stanje telefona. Odbijanje dolaznog poziva. + U tijeku je mobilni poziv. Odbijanje dolaznog poziva. + U tijeku je mobilni poziv. Poziv završen. OK Registracija poziva Prijave poziva su u tijeku diff --git a/infobip-rtc-ui/src/main/res/values-hu/strings.xml b/infobip-rtc-ui/src/main/res/values-hu/strings.xml index 1f6ec18e..34aebf17 100644 --- a/infobip-rtc-ui/src/main/res/values-hu/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-hu/strings.xml @@ -1,5 +1,7 @@ A hívások kezeléséhez audioengedély szükséges. + Bluetooth csatlakozási engedély szükséges a Bluetooth-hoz csatlakoztatott eszköz hívásokhoz való használatához. + Telefonállapot-engedély szükséges a párhuzamos WebRTC és mobilhívások elkerüléséhez. A hívás befejeződött A hálózat rossz hívásminőséget okoz Hívások @@ -13,6 +15,9 @@ Tompított Néma Hiányzó értesítési engedély. Csökkenő bejövő hívás. + Hiányzó telefonállapotra vonatkozó engedély. Csökkenő bejövő hívás. + Folyamatban van a mobilhívás. Csökkenő bejövő hívás. + Folyamatban van a mobilhívás. A hívás befejeződött. Rendben Hívás regisztráció A hívás regisztrációja folyamatban van diff --git a/infobip-rtc-ui/src/main/res/values-in/strings.xml b/infobip-rtc-ui/src/main/res/values-in/strings.xml index 55adbc72..e21280f1 100644 --- a/infobip-rtc-ui/src/main/res/values-in/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-in/strings.xml @@ -1,6 +1,8 @@ Izin audio diperlukan untuk menangani panggilan. + Izin koneksi Bluetooth diperlukan untuk menggunakan perangkat Anda yang terhubung Bluetooth untuk panggilan. + Izin negara telepon diperlukan untuk menghindari panggilan WebRTC dan seluler paralel. Panggilan selesai Pendaftaran panggilan Registrasi panggilan sedang berlangsung @@ -16,6 +18,9 @@ Meredam Bisu Izin notifikasi tidak ada. Menolak panggilan masuk. + Izin negara telepon tidak ada. Menolak panggilan masuk. + Ada panggilan seluler yang sedang berlangsung. Menolak panggilan masuk. + Ada panggilan seluler yang sedang berlangsung. Panggilan selesai. Oke Berbagi layar Berbagi layar diff --git a/infobip-rtc-ui/src/main/res/values-it/strings.xml b/infobip-rtc-ui/src/main/res/values-it/strings.xml index f23b532c..4e9cf5d5 100644 --- a/infobip-rtc-ui/src/main/res/values-it/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-it/strings.xml @@ -1,5 +1,7 @@ Per gestire le chiamate è necessaria l\'autorizzazione audio. + Per utilizzare il dispositivo connesso Bluetooth per le chiamate è necessaria l\'autorizzazione alla connessione Bluetooth. + È necessaria l\'autorizzazione dello stato del telefono per evitare chiamate WebRTC e cellulari parallele. Chiamata terminata La tua rete sta causando una scarsa qualità delle chiamate Chiamate @@ -13,6 +15,9 @@ Muto Muto Autorizzazione di notifica mancante. Chiamata in arrivo rifiutata. + Autorizzazione dello stato del telefono mancante. Rifiuto della chiamata in arrivo. + C\'è una chiamata cellulare in corso. Rifiuto della chiamata in arrivo. + C\'è una chiamata cellulare in corso. Chiamata terminata. Ok Registrazione chiamata La registrazione della chiamata è in corso diff --git a/infobip-rtc-ui/src/main/res/values-iw/strings.xml b/infobip-rtc-ui/src/main/res/values-iw/strings.xml index 34586286..469c6885 100644 --- a/infobip-rtc-ui/src/main/res/values-iw/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-iw/strings.xml @@ -1,5 +1,7 @@ נדרשת הרשאת שמע לטיפול בשיחות. + נדרשת הרשאת חיבור Bluetooth כדי להשתמש במכשיר המחובר ל-Bluetooth שלך לשיחות. + נדרשת הרשאת מצב טלפון כדי למנוע שיחות WebRTC ושיחות סלולריות מקבילות. השיחה הסתיימה הרשת שלך גורמת לאיכות שיחה ירודה שיחות @@ -13,6 +15,9 @@ מושתק לְהַשְׁתִיק חסרה הרשאת הודעה. דחיית שיחה נכנסת. + חסרה הרשאת מצב טלפון. דחיית שיחה נכנסת. + יש שיחה סלולרית מתמשכת. דחיית שיחה נכנסת. + יש שיחה סלולרית מתמשכת. השיחה הסתיימה. בסדר רישום שיחות רישום השיחה בעיצומה diff --git a/infobip-rtc-ui/src/main/res/values-ja/strings.xml b/infobip-rtc-ui/src/main/res/values-ja/strings.xml index ddb959f8..8baca328 100644 --- a/infobip-rtc-ui/src/main/res/values-ja/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-ja/strings.xml @@ -1,5 +1,7 @@ 通話を処理するには、音声の許可が必要です。 + Bluetooth 接続デバイスを通話に使用するには、Bluetooth 接続許可が必要です。 + WebRTC と携帯電話の同時通話を回避するには、電話状態の許可が必要です。 通話終了 ネットワークが原因で通話品質が低下している 通話 @@ -13,6 +15,9 @@ ミュート ミュート 通知権限がありません。 着信を拒否します。 + 電話の状態の許可がありません。着信を拒否します。 + 携帯電話の通話が継続中です。着信を拒否します。 + 携帯電話の通話が継続中です。通話が終了しました。 Ok 通話登録 通話登録中です diff --git a/infobip-rtc-ui/src/main/res/values-ko/strings.xml b/infobip-rtc-ui/src/main/res/values-ko/strings.xml index 50c2ce04..d66497be 100644 --- a/infobip-rtc-ui/src/main/res/values-ko/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-ko/strings.xml @@ -1,5 +1,7 @@ 통화를 처리하려면 오디오 권한이 필요합니다. + 블루투스 연결 기기를 사용하여 통화를 하기 위해서는 블루투스 연결 권한이 필요합니다. + 병렬 WebRTC 및 셀룰러 통화를 방지하려면 전화 상태 권한이 필요합니다. 통화 완료 네트워크로 인해 통화 품질이 저하됩니다. 통화 @@ -13,6 +15,9 @@ 음소거 음소거 알림 권한이 없습니다. 수신 전화를 거부합니다. + 전화 상태 권한이 없습니다. 수신 전화를 거절하고 있습니다. + 진행 중인 휴대폰 통화가 있습니다. 수신 전화를 거절하고 있습니다. + 진행 중인 휴대폰 통화가 있습니다. 통화가 완료되었습니다. 확인 전화 등록 통화 등록이 진행 중입니다. diff --git a/infobip-rtc-ui/src/main/res/values-ms/strings.xml b/infobip-rtc-ui/src/main/res/values-ms/strings.xml index cbd0f753..7d832949 100644 --- a/infobip-rtc-ui/src/main/res/values-ms/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-ms/strings.xml @@ -1,5 +1,7 @@ Kebenaran audio diperlukan untuk mengendalikan panggilan. + Kebenaran sambungan Bluetooth diperlukan untuk menggunakan peranti bersambung Bluetooth anda untuk panggilan. + Kebenaran keadaan telefon diperlukan untuk mengelakkan panggilan WebRTC dan selular selari. Panggilan selesai Rangkaian anda menyebabkan kualiti panggilan yang lemah Panggilan @@ -13,6 +15,9 @@ Disenyapkan bisu Kebenaran pemberitahuan tiada. Menolak panggilan masuk. + Kebenaran keadaan telefon hilang. Menolak panggilan masuk. + Terdapat panggilan selular yang sedang berjalan. Menolak panggilan masuk. + Terdapat panggilan selular yang sedang berjalan. Panggilan selesai. Okey Pendaftaran panggilan Pendaftaran panggilan sedang dijalankan diff --git a/infobip-rtc-ui/src/main/res/values-nl/strings.xml b/infobip-rtc-ui/src/main/res/values-nl/strings.xml index a3d60213..bd0b230a 100644 --- a/infobip-rtc-ui/src/main/res/values-nl/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-nl/strings.xml @@ -1,5 +1,7 @@ Audiotoestemming is vereist om gesprekken af te handelen. + Er is toestemming voor Bluetooth-verbinding vereist om uw met Bluetooth verbonden apparaat te kunnen gebruiken voor oproepen. + Toestemming van de telefoonstatus is vereist om parallelle WebRTC- en mobiele oproepen te voorkomen. Oproep beëindigd Uw netwerk veroorzaakt een slechte gesprekskwaliteit Oproepen @@ -13,6 +15,9 @@ Gedempt Stom Ontbrekende machtiging voor meldingen. Inkomende oproep weigeren. + Ontbrekende toestemming voor de telefoonstatus. Inkomende oproep weigeren. + Er is een voortdurend mobiel gesprek. Inkomende oproep weigeren. + Er is een voortdurend mobiel gesprek. Gesprek beëindigd. OK Bel registratie Oproepregistratie is bezig diff --git a/infobip-rtc-ui/src/main/res/values-pl/strings.xml b/infobip-rtc-ui/src/main/res/values-pl/strings.xml index 8c0e807a..b4ba8cd0 100644 --- a/infobip-rtc-ui/src/main/res/values-pl/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-pl/strings.xml @@ -1,5 +1,7 @@ Do obsługi połączeń wymagane jest zezwolenie na dźwięk. + Aby używać urządzenia podłączonego przez Bluetooth do wykonywania połączeń, wymagane jest zezwolenie na połączenie Bluetooth. + Aby uniknąć równoległych połączeń WebRTC i komórkowych, wymagane jest zezwolenie na stan telefonu. Rozmowa zakończona Twoja sieć powoduje niską jakość połączenia Połączenia @@ -13,6 +15,9 @@ Wyciszony Niemy Brak uprawnień do powiadomień. Odrzucanie połączenia przychodzącego. + Brak pozwolenia na stan telefonu. Odrzucanie połączenia przychodzącego. + Trwa rozmowa komórkowa. Odrzucanie połączenia przychodzącego. + Trwa rozmowa komórkowa. Rozmowa zakończona. Ok Rejestracja połączeń Trwa rejestracja rozmów diff --git a/infobip-rtc-ui/src/main/res/values-pt/strings.xml b/infobip-rtc-ui/src/main/res/values-pt/strings.xml index c35df0fa..541366e7 100644 --- a/infobip-rtc-ui/src/main/res/values-pt/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-pt/strings.xml @@ -1,5 +1,7 @@ É necessária permissão de áudio para lidar com chamadas. + É necessária permissão de conexão Bluetooth para usar seu dispositivo conectado por Bluetooth para chamadas. + A permissão de estado do telefone é necessária para evitar chamadas paralelas de WebRTC e de celular. Chamada concluída Sua rede está causando baixa qualidade de chamada Chamadas @@ -13,6 +15,9 @@ Silenciado Mudo Permissão de notificação ausente. Recusando chamada recebida. + Permissão de estado do telefone ausente. Recusando chamada recebida. + Há uma chamada de celular em andamento. Recusando chamada recebida. + Há uma chamada de celular em andamento. Chamada finalizada. OK Registro de chamadas O registro da chamada está em andamento diff --git a/infobip-rtc-ui/src/main/res/values-ro/strings.xml b/infobip-rtc-ui/src/main/res/values-ro/strings.xml index 0bdfffc7..7522bec4 100644 --- a/infobip-rtc-ui/src/main/res/values-ro/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-ro/strings.xml @@ -1,5 +1,7 @@ Este necesară permisiune audio pentru a gestiona apelurile. + Este necesară permisiunea de conectare Bluetooth pentru a utiliza dispozitivul conectat prin Bluetooth pentru apeluri. + Este necesară permisiunea de stare a telefonului pentru a evita apelurile paralele WebRTC și celulare. Apel terminat Rețeaua dvs. cauzează o calitate slabă a apelurilor Apeluri @@ -13,6 +15,9 @@ Dezactivat Mut Lipsește permisiunea de notificare. Se refuză apelul primit. + Lipsește permisiunea de stare a telefonului. Se refuză apelul primit. + Există un apel mobil în curs. Se refuză apelul primit. + Există un apel mobil în curs. Apel terminat. O.K Înregistrare apel Înregistrarea apelului este în curs diff --git a/infobip-rtc-ui/src/main/res/values-ru/strings.xml b/infobip-rtc-ui/src/main/res/values-ru/strings.xml index cb7bf06e..76b70318 100644 --- a/infobip-rtc-ui/src/main/res/values-ru/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-ru/strings.xml @@ -1,5 +1,7 @@ Разрешение на аудио требуется для обработки вызовов. + Разрешение на подключение Bluetooth требуется для использования устройства, подключенного по Bluetooth, для вызовов. + Разрешение на состояние телефона необходимо, чтобы избежать параллельных вызовов WebRTC и сотовой связи. Вызов завершен Ваша сеть вызывает плохое качество связи Звонки @@ -13,6 +15,9 @@ Приглушен Немой Отсутствует разрешение на уведомление. Отклонение входящего вызова. + Отсутствует разрешение штата телефона. Отклонение входящего звонка. + Продолжается звонок на сотовый. Отклонение входящего звонка. + Продолжается звонок на сотовый. Вызов завершен. Ok Регистрация звонков Регистрация звонка выполняется diff --git a/infobip-rtc-ui/src/main/res/values-sk/strings.xml b/infobip-rtc-ui/src/main/res/values-sk/strings.xml index 09c8bd3a..548871e7 100644 --- a/infobip-rtc-ui/src/main/res/values-sk/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-sk/strings.xml @@ -1,5 +1,7 @@ Na používanie hovorov sa vyžaduje povolenie na zvuk. + Na používanie zariadenia pripojeného cez Bluetooth na hovory sa vyžaduje povolenie Bluetooth pripojenia. + Vyžaduje sa povolenie stavu telefónu, aby sa zabránilo paralelným WebRTC a mobilným hovorom. Hovor ukončený Vaša sieť spôsobuje zlú kvalitu hovoru Hovory @@ -13,6 +15,9 @@ Stlmené Stlmiť zvuk Chýbajúce povolenie notifikácii. Odmietam prichádzajúci hovor. + Chýba povolenie stavu telefónu. Odmietam prichádzajúci hovor. + Prebieha mobilný hovor. Odmietam prichádzajúci hovor. + Prebieha mobilný hovor. Hovor ukončený. Ok Registrácia hovoru Prebieha registrácia hovoru diff --git a/infobip-rtc-ui/src/main/res/values-sq/strings.xml b/infobip-rtc-ui/src/main/res/values-sq/strings.xml index b02cfb9b..bb2b5e95 100644 --- a/infobip-rtc-ui/src/main/res/values-sq/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-sq/strings.xml @@ -1,5 +1,7 @@ Kërkohet leje audio për të trajtuar telefonatat. + Kërkohet leja e lidhjes Bluetooth për të përdorur pajisjen tuaj të lidhur me Bluetooth për telefonata. + Kërkohet leja e gjendjes së telefonit për të shmangur thirrjet paralele WebRTC dhe celulare. Telefonata përfundoi Rrjeti juaj po shkakton cilësi të dobët të thirrjeve Thirrjet @@ -13,6 +15,9 @@ I heshtur Memec Mungon leja e njoftimit. Refuzimi i thirrjes hyrëse. + Mungon leja e gjendjes së telefonit. Refuzimi i thirrjes hyrëse. + Ka një telefonatë celulare në vazhdim. Refuzimi i thirrjes hyrëse. + Ka një telefonatë celulare në vazhdim. Telefonata përfundoi. Ne rregull Regjistrimi i thirrjeve Regjistrimi i thirrjeve është në proces diff --git a/infobip-rtc-ui/src/main/res/values-sr/strings.xml b/infobip-rtc-ui/src/main/res/values-sr/strings.xml index 1f804601..835ee347 100644 --- a/infobip-rtc-ui/src/main/res/values-sr/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-sr/strings.xml @@ -1,5 +1,7 @@ Audio dozvola zahteva preuzimanje poziva. + Bluetooth dozvola za povezivanje je potrebna za korišćenje Bluetooth povezanog uređaja za pozive. + Potrebna je dozvola za stanje telefona da bi se izbegli paralelni WebRTC i mobilni pozivi. Poziv završen Vaš internet izaziva loš kvalitet poziva Poziv @@ -13,6 +15,9 @@ Mutirano Isključen zvuk Absentis notitia licentia. Declinatio advenientis voco. + Nedostaje dozvola telefonskog stanja. Odbijanje dolaznog poziva. + U toku je mobilni poziv. Odbijanje dolaznog poziva. + U toku je mobilni poziv. Poziv je završen. U redu Vocationem registration Adaequationis user notitia @@ -26,8 +31,8 @@ Video Video poziv Dozvolite kameru za video poziv. + Vaš mikrofon je mutiran. Delite ekran. Zaustavite deljenje ekrana - Vaš mikrofon je mutiran. Poziv je prekinut. Pokušavam da se ponovo povežem… \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/res/values-sv/strings.xml b/infobip-rtc-ui/src/main/res/values-sv/strings.xml index eed2ff06..efbd2e72 100644 --- a/infobip-rtc-ui/src/main/res/values-sv/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-sv/strings.xml @@ -1,5 +1,7 @@ Ljudtillstånd krävs för att hantera samtal. + Bluetooth-anslutningsbehörighet krävs för att använda din Bluetooth-anslutna enhet för samtal. + Tillstånd för telefonstatus krävs för att undvika parallella WebRTC- och mobilsamtal. Samtalet avslutat Ditt nätverk orsakar dålig samtalskvalitet Samtal @@ -13,6 +15,9 @@ Dämpad Stum Aviseringsbehörighet saknas. Avvisar inkommande samtal. + Tillstånd för telefonstatus saknas. Avvisar inkommande samtal. + Det pågår mobilsamtal. Avvisar inkommande samtal. + Det pågår mobilsamtal. Samtalet avslutat. Ok Ring registrering Samtalsregistrering pågår diff --git a/infobip-rtc-ui/src/main/res/values-th/strings.xml b/infobip-rtc-ui/src/main/res/values-th/strings.xml index 02d2345c..399b75ca 100644 --- a/infobip-rtc-ui/src/main/res/values-th/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-th/strings.xml @@ -1,5 +1,7 @@ ต้องมีการอนุญาตเสียงเพื่อจัดการการโทร + จำเป็นต้องได้รับอนุญาตในการเชื่อมต่อ Bluetooth เพื่อใช้อุปกรณ์ที่เชื่อมต่อ Bluetooth ของคุณสำหรับการโทร + ต้องได้รับอนุญาตจากสถานะโทรศัพท์เพื่อหลีกเลี่ยงการโทรผ่าน WebRTC และโทรศัพท์มือถือแบบขนาน โทรเสร็จแล้ว เครือข่ายของคุณทำให้คุณภาพการโทรไม่ดี โทร @@ -13,6 +15,9 @@ ปิดเสียง ปิดเสียง ไม่มีสิทธิ์การแจ้งเตือน ปฏิเสธสายเรียกเข้า + ไม่มีการอนุญาตสถานะโทรศัพท์ ปฏิเสธสายเรียกเข้า + มีการโทรมือถืออย่างต่อเนื่อง ปฏิเสธสายเรียกเข้า + มีการโทรมือถืออย่างต่อเนื่อง โทรเสร็จแล้ว. ตกลง โทรลงทะเบียน อยู่ระหว่างการลงทะเบียนการโทร diff --git a/infobip-rtc-ui/src/main/res/values-tr/strings.xml b/infobip-rtc-ui/src/main/res/values-tr/strings.xml index fdf1ce7b..a954462c 100644 --- a/infobip-rtc-ui/src/main/res/values-tr/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-tr/strings.xml @@ -1,5 +1,7 @@ Çağrıları işlemek için ses izni gereklidir. + Bluetooth bağlantılı cihazınızı aramalar amacıyla kullanmak için Bluetooth bağlantı izni gereklidir. + Paralel WebRTC ve hücresel aramalardan kaçınmak için telefon durumu izni gereklidir. arama bitti Ağınız düşük arama kalitesine neden oluyor aramalar @@ -13,6 +15,9 @@ sessiz Sesini kapatmak Eksik bildirim izni. Gelen çağrı reddediliyor. + Telefon durumu izni eksik. Gelen çağrı reddediliyor. + Devam eden bir cep telefonu görüşmesi var. Gelen çağrı reddediliyor. + Devam eden bir cep telefonu görüşmesi var. Arama bitti. Tamam Çağrı kaydı Çağrı kaydı devam ediyor diff --git a/infobip-rtc-ui/src/main/res/values-uk/strings.xml b/infobip-rtc-ui/src/main/res/values-uk/strings.xml index 9fd03b51..24e33b4e 100644 --- a/infobip-rtc-ui/src/main/res/values-uk/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-uk/strings.xml @@ -1,5 +1,7 @@ Для обробки викликів потрібен дозвіл на звук. + Дозвіл на підключення Bluetooth потрібен, щоб використовувати пристрій, підключений Bluetooth, для дзвінків. + Дозвіл на стан телефону потрібен, щоб уникнути паралельних викликів WebRTC і стільникових дзвінків. Дзвінок завершено Ваша мережа спричиняє низьку якість зв’язку Дзвінки @@ -13,6 +15,9 @@ Вимкнено Вимкнути звук Відсутній дозвіл на сповіщення. Відхилення вхідного дзвінка. + Відсутній дозвіл стану телефону. Відхилення вхідного дзвінка. + Триває мобільний виклик. Відхилення вхідного дзвінка. + Триває мобільний виклик. Дзвінок завершено. В порядку Реєстрація дзвінка Триває реєстрація дзвінків diff --git a/infobip-rtc-ui/src/main/res/values-vi/strings.xml b/infobip-rtc-ui/src/main/res/values-vi/strings.xml index 14e4d4a7..8feba285 100644 --- a/infobip-rtc-ui/src/main/res/values-vi/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-vi/strings.xml @@ -1,5 +1,7 @@ Quyền âm thanh được yêu cầu để xử lý cuộc gọi. + Cần có quyền kết nối Bluetooth để sử dụng thiết bị được kết nối Bluetooth của bạn cho các cuộc gọi. + Cần có quyền trạng thái điện thoại để tránh các cuộc gọi di động và WebRTC song song. Cuộc gọi đã kết thúc Mạng của bạn đang gây ra chất lượng cuộc gọi kém Cuộc gọi @@ -13,6 +15,9 @@ Đã tắt tiếng Tắt tiếng Thiếu quyền thông báo. Đang từ chối cuộc gọi đến. + Thiếu quyền trạng thái điện thoại. Đang từ chối cuộc gọi đến. + Có cuộc gọi di động đang diễn ra. Đang từ chối cuộc gọi đến. + Có cuộc gọi di động đang diễn ra. Cuộc gọi kết thúc. Được đăng ký cuộc gọi Đang tiến hành đăng ký cuộc gọi diff --git a/infobip-rtc-ui/src/main/res/values-zh-rCN/strings.xml b/infobip-rtc-ui/src/main/res/values-zh-rCN/strings.xml index d344a373..f625d8c9 100644 --- a/infobip-rtc-ui/src/main/res/values-zh-rCN/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-zh-rCN/strings.xml @@ -1,6 +1,7 @@ - 需要音频权限来处理通话 + 需要蓝牙连接权限才能使用蓝牙连接设备进行通话。 + 需要电话状态许可才能避免并行 WebRTC 和蜂窝呼叫。 通话结束 您的网络质量差 通话 @@ -14,6 +15,9 @@ 闭麦 关闭麦克风 缺少通知权限。拒绝来电。 + 缺少电话状态许可。拒绝来电。 + 手机通话正在进行中。拒绝来电。 + 手机通话正在进行中。通话完毕。 好的 致电登记 来电登记正在进行中 @@ -31,4 +35,4 @@ 您正在共享一个屏幕。 停止共享屏幕 通话被挂断。正在尝试重新连接... - + \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/res/values-zh-rHK/strings.xml b/infobip-rtc-ui/src/main/res/values-zh-rHK/strings.xml index c5d21459..b5e6c124 100644 --- a/infobip-rtc-ui/src/main/res/values-zh-rHK/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-zh-rHK/strings.xml @@ -1,13 +1,11 @@ - 處理呼叫需要音頻權限。 + 需要藍牙連線權限才能使用藍牙連接裝置進行通話。 + 需要電話狀態許可才能避免並行 WebRTC 和蜂窩呼叫。 通話結束 - 來電登記 - 來電登記正在進行中 互聯網連接較弱 來電 取消 - 通話被掛斷。正在嘗試重新連線… 翻轉相機 掛斷 通話中 @@ -17,18 +15,24 @@ 靜音 沉默的 缺少通知權限。 拒絕來電。 + 缺少電話狀態許可。拒絕來電。 + 手機通話正在進行中。拒絕來電。 + 手機通話正在進行中。通話完畢。 + 來電登記 + 來電登記正在進行中 屏幕共享 屏幕共享 您的屏幕已共享 喇叭 - 停止共享螢幕 未知 未知錯誤 取消靜音 視頻 視訊通話 視頻通話需要攝像頭權限。 - 您正在共享一個螢幕。 您的麥克風已靜音 - + 您正在共享一個螢幕。 + 停止共享螢幕 + 通話被掛斷。正在嘗試重新連線… + \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/res/values-zh-rSG/strings.xml b/infobip-rtc-ui/src/main/res/values-zh-rSG/strings.xml index d344a373..f625d8c9 100644 --- a/infobip-rtc-ui/src/main/res/values-zh-rSG/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-zh-rSG/strings.xml @@ -1,6 +1,7 @@ - 需要音频权限来处理通话 + 需要蓝牙连接权限才能使用蓝牙连接设备进行通话。 + 需要电话状态许可才能避免并行 WebRTC 和蜂窝呼叫。 通话结束 您的网络质量差 通话 @@ -14,6 +15,9 @@ 闭麦 关闭麦克风 缺少通知权限。拒绝来电。 + 缺少电话状态许可。拒绝来电。 + 手机通话正在进行中。拒绝来电。 + 手机通话正在进行中。通话完毕。 好的 致电登记 来电登记正在进行中 @@ -31,4 +35,4 @@ 您正在共享一个屏幕。 停止共享屏幕 通话被挂断。正在尝试重新连接... - + \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/res/values-zh-rTW/strings.xml b/infobip-rtc-ui/src/main/res/values-zh-rTW/strings.xml index c5d21459..b5e6c124 100644 --- a/infobip-rtc-ui/src/main/res/values-zh-rTW/strings.xml +++ b/infobip-rtc-ui/src/main/res/values-zh-rTW/strings.xml @@ -1,13 +1,11 @@ - 處理呼叫需要音頻權限。 + 需要藍牙連線權限才能使用藍牙連接裝置進行通話。 + 需要電話狀態許可才能避免並行 WebRTC 和蜂窩呼叫。 通話結束 - 來電登記 - 來電登記正在進行中 互聯網連接較弱 來電 取消 - 通話被掛斷。正在嘗試重新連線… 翻轉相機 掛斷 通話中 @@ -17,18 +15,24 @@ 靜音 沉默的 缺少通知權限。 拒絕來電。 + 缺少電話狀態許可。拒絕來電。 + 手機通話正在進行中。拒絕來電。 + 手機通話正在進行中。通話完畢。 + 來電登記 + 來電登記正在進行中 屏幕共享 屏幕共享 您的屏幕已共享 喇叭 - 停止共享螢幕 未知 未知錯誤 取消靜音 視頻 視訊通話 視頻通話需要攝像頭權限。 - 您正在共享一個螢幕。 您的麥克風已靜音 - + 您正在共享一個螢幕。 + 停止共享螢幕 + 通話被掛斷。正在嘗試重新連線… + \ No newline at end of file diff --git a/infobip-rtc-ui/src/main/res/values/strings.xml b/infobip-rtc-ui/src/main/res/values/strings.xml index 7cbcd768..da6b7687 100644 --- a/infobip-rtc-ui/src/main/res/values/strings.xml +++ b/infobip-rtc-ui/src/main/res/values/strings.xml @@ -1,6 +1,8 @@ Audio permission is required to handle calls. + Bluetooth connect permission is required to use your Bluetooth connected device for calls. + Phone state permission is required to avoid parallel WebRTC and cellular calls. Call finished Your network is causing poor call quality Calls @@ -14,6 +16,9 @@ Muted Mute Missing notification permission. Declining incoming call. + Missing phone state permission. Declining incoming call. + There is ongoing cellular call. Declining incoming call. + There is ongoing cellular call. Call finished. Ok Call registration Call registration is in progress