diff --git a/README.md b/README.md index cf2710d..d72a4c1 100644 --- a/README.md +++ b/README.md @@ -79,19 +79,19 @@ This demo support Android device with **Android 7.0** or later - For Full SDK ``` dependencies { - implementation 'com.ciscowebex:webexsdk:3.11.3' + implementation 'com.ciscowebex:webexsdk:3.12.0' } ``` - For Meeting SDK ``` dependencies { - implementation 'com.ciscowebex:webexsdk-meeting:3.11.3' + implementation 'com.ciscowebex:webexsdk-meeting:3.12.0' } ``` - For WebexCalling SDK ``` dependencies { - implementation 'com.ciscowebex:webexsdk-wxc:3.11.3' + implementation 'com.ciscowebex:webexsdk-wxc:3.12.0' } ``` diff --git a/app/build.gradle b/app/build.gradle index 59051c5..f8ae791 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,8 +32,8 @@ android { applicationId "com.cisco.sdk_android" minSdkVersion Versions.minSdk targetSdkVersion Versions.targetSdk - versionCode 3110300 - versionName "3.11.3" + versionCode 3120000 + versionName "3.12.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -121,9 +121,9 @@ android { dependencies { //At a time only one WebexSDK should be used. - implementation 'com.ciscowebex:webexsdk:3.11.3' // For full flavor - //implementation 'com.ciscowebex:webexsdk-wxc:3.11.3' //For webexCalling flavor - //implementation 'com.ciscowebex:webexsdk-meeting:3.11.3' // For meeting flavor + implementation 'com.ciscowebex:webexsdk:3.12.0' // For full flavor + //implementation 'com.ciscowebex:webexsdk-wxc:3.12.0' //For webexCalling flavor + //implementation 'com.ciscowebex:webexsdk-meeting:3.12.0' // For meeting flavor implementation fileTree(dir: "libs", include: ["*.jar"]) implementation Dependencies.kotlinStdLib diff --git a/app/src/androidTest/java/com/ciscowebex/androidsdk/kitchensink/teams/membership/TeamFragmentTest.kt b/app/src/androidTest/java/com/ciscowebex/androidsdk/kitchensink/teams/membership/TeamFragmentTest.kt index f8ce36a..b7dbea5 100644 --- a/app/src/androidTest/java/com/ciscowebex/androidsdk/kitchensink/teams/membership/TeamFragmentTest.kt +++ b/app/src/androidTest/java/com/ciscowebex/androidsdk/kitchensink/teams/membership/TeamFragmentTest.kt @@ -74,13 +74,13 @@ class TeamFragmentTest : KitchenSinkTest() { onView(withId(R.id.teamsRecyclerView)).check(matches(hasDescendant(withText(testTeam)))) } - @Test - fun addPersonToTeam_teamFragment(){ - goToMessagingActivity() - onView(withId(R.id.teamsRecyclerView)).perform(RecyclerViewActions.actionOnItemAtPosition(0, clickChildViewWithId(R.id.iv_add_to_team))) - WaitUtils.sleep(TIME_1_SEC) - intended(hasComponent(MessagingSearchActivity::class.java.name)) - } +// @Test +// fun addPersonToTeam_teamFragment(){ +// goToMessagingActivity() +// onView(withId(R.id.teamsRecyclerView)).perform(RecyclerViewActions.actionOnItemAtPosition(0, clickChildViewWithId(R.id.iv_add_to_team))) +// WaitUtils.sleep(TIME_1_SEC) +// intended(hasComponent(MessagingSearchActivity::class.java.name)) +// } @Test fun testBottomSheetOptions_teamsFragment(){ diff --git a/app/src/full/java/com/ciscowebex/androidsdk/kitchensink/annotation/AnnotationRenderer.kt b/app/src/full/java/com/ciscowebex/androidsdk/kitchensink/annotation/AnnotationRenderer.kt new file mode 100644 index 0000000..8aa4c4e --- /dev/null +++ b/app/src/full/java/com/ciscowebex/androidsdk/kitchensink/annotation/AnnotationRenderer.kt @@ -0,0 +1,32 @@ +package com.ciscowebex.androidsdk.kitchensink.annotation + +import android.content.Context +import android.widget.FrameLayout +import com.ciscowebex.androidsdk.annotation.renderer.LiveAnnotationRenderer + +class AnnotationRenderer(requireContext: Context) { + private val renderer = LiveAnnotationRenderer(requireContext) + + interface AnnotationRendererCallback: LiveAnnotationRenderer.LiveAnnotationRendererCallback{ + } + + fun startRendering(): Boolean { + return renderer.startRendering() + } + + fun stopRendering() { + renderer.stopRendering() + } + + fun renderData(data: String) { + renderer.renderData(data) + } + + fun getAnnotationLayer(): FrameLayout? { + return renderer.getAnnotationLayer() + } + + fun setAnnotationRendererCallback(callback: AnnotationRendererCallback) { + renderer.setAnnotationRendererCallback(callback) + } +} \ No newline at end of file diff --git a/app/src/full/res/values/strings.xml b/app/src/full/res/values/strings.xml new file mode 100644 index 0000000..d37539f --- /dev/null +++ b/app/src/full/res/values/strings.xml @@ -0,0 +1,4 @@ + + Kitchen Sink + Kitchen Sink + diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/WebexRepository.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/WebexRepository.kt index 0d1f901..a32710f 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/WebexRepository.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/WebexRepository.kt @@ -701,6 +701,15 @@ class WebexRepository(val webex: Webex) : WebexUCLoginDelegate, WebexAuthDelegat } } } + + override fun onMoveMeetingFailed(call: Call?) { + val observers: MutableList? = _callObservers[_callId] + observers?.let { it -> + it.forEach { observer -> + observer.onMoveMeetingFailed(call) + } + } + } } private fun registerCallObserver(call: Call) { diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/WebexViewModel.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/WebexViewModel.kt index 4dc83ad..3f3b94c 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/WebexViewModel.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/WebexViewModel.kt @@ -12,7 +12,6 @@ import com.ciscowebex.androidsdk.kitchensink.firebase.RegisterTokenService import com.ciscowebex.androidsdk.kitchensink.person.PersonModel import com.ciscowebex.androidsdk.CompletionHandler import com.ciscowebex.androidsdk.WebexError -import com.ciscowebex.androidsdk.annotation.renderer.LiveAnnotationRenderer import com.google.android.gms.tasks.OnCompleteListener import com.google.android.gms.tasks.Task import com.google.firebase.messaging.FirebaseMessaging @@ -21,6 +20,7 @@ import com.ciscowebex.androidsdk.auth.PhoneServiceRegistrationFailureReason import com.ciscowebex.androidsdk.auth.TokenAuthenticator import com.ciscowebex.androidsdk.auth.UCLoginServerConnectionStatus import com.ciscowebex.androidsdk.internal.ResultImpl +import com.ciscowebex.androidsdk.kitchensink.annotation.AnnotationRenderer import com.ciscowebex.androidsdk.kitchensink.calling.CallObserverInterface import com.ciscowebex.androidsdk.kitchensink.utils.CallObjectStorage import com.ciscowebex.androidsdk.kitchensink.utils.Constants @@ -45,6 +45,7 @@ import com.ciscowebex.androidsdk.phone.MediaStream import com.ciscowebex.androidsdk.phone.MediaStreamQuality import com.ciscowebex.androidsdk.phone.BreakoutSession import com.ciscowebex.androidsdk.phone.Breakout +import com.ciscowebex.androidsdk.phone.CompanionMode import com.ciscowebex.androidsdk.phone.DirectTransferResult import com.ciscowebex.androidsdk.phone.InviteParticipantError import com.ciscowebex.androidsdk.phone.SwitchToAudioVideoCallResult @@ -671,8 +672,8 @@ class WebexViewModel(val webex: Webex, val repository: WebexRepository) : BaseVi }) } - private var annotationRenderer: LiveAnnotationRenderer? = null - fun initalizeAnnotations(renderer: LiveAnnotationRenderer) { + private var annotationRenderer: AnnotationRenderer? = null + fun initalizeAnnotations(renderer: AnnotationRenderer) { getCall(currentCallId.orEmpty())?.getLiveAnnotationHandle()?.let {annotations-> annotations.setLiveAnnotationsPolicy(LiveAnnotationsPolicy.NeedAskForAnnotate){ @@ -694,7 +695,7 @@ class WebexViewModel(val webex: Webex, val repository: WebexRepository) : BaseVi override fun onLiveAnnotationsStarted() { annotationRenderer = renderer.apply { - setAnnotationRendererCallback(object : LiveAnnotationRenderer.LiveAnnotationRendererCallback { + setAnnotationRendererCallback(object : AnnotationRenderer.AnnotationRendererCallback { override fun onAnnotationRenderingReady() { Log.d(tag, "onAnnotationRenderingReady") } diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallActivity.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallActivity.kt index 20ac11c..ebff5c4 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallActivity.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallActivity.kt @@ -45,11 +45,12 @@ class CallActivity : BaseActivity(), CallControlsFragment.OnCallActionListener, companion object { - fun getOutgoingIntent(context: Context, callerName: String, callType: Boolean): Intent { + fun getOutgoingIntent(context: Context, callerName: String, callType: Boolean, moveMeeting: Boolean): Intent { val intent = Intent(context, CallActivity::class.java) intent.putExtra(Constants.Intent.CALLING_ACTIVITY_ID, 0) intent.putExtra(Constants.Intent.OUTGOING_CALL_CALLER_ID, callerName) intent.putExtra(Constants.Intent.CALL_TYPE, callType) + intent.putExtra(Constants.Intent.MOVE_MEETING, moveMeeting) return intent } fun getIncomingIntent(context: Context, callId: String? = null): Intent { @@ -87,8 +88,10 @@ class CallActivity : BaseActivity(), CallControlsFragment.OnCallActionListener, if (callingActivity == 0) { val callerId = intent.getStringExtra(Constants.Intent.OUTGOING_CALL_CALLER_ID) val switchToUcmOrBroadworksCall = intent.getBooleanExtra(Constants.Intent.CALL_TYPE, false) + val moveMeeting = intent.getBooleanExtra(Constants.Intent.MOVE_MEETING, false) + val companionMode: CompanionMode = if (moveMeeting) CompanionMode.MoveMeeting else CompanionMode.None callerId?.let { - fragment.dialOutgoingCall(callerId, isCucmOrWxcCall = switchToUcmOrBroadworksCall) + fragment.dialOutgoingCall(callerId, isCucmOrWxcCall = switchToUcmOrBroadworksCall, moveMeeting = companionMode) } } else if (intent.action == Constants.Action.WEBEX_CALL_ACTION){ intent?.getStringExtra(Constants.Intent.CALL_ID) ?.let { callId -> @@ -469,6 +472,10 @@ class CallActivity : BaseActivity(), CallControlsFragment.OnCallActionListener, TODO("Not yet implemented") } + override fun onMoveMeetingFailed(call: Call?) { + TODO("Not yet implemented") + } + override fun finish() { if(calls.size > 0){ //Resume a queued call diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallControlsFragment.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallControlsFragment.kt index 85724a5..2349501 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallControlsFragment.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallControlsFragment.kt @@ -45,11 +45,11 @@ import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide import com.ciscowebex.androidsdk.CompletionHandler import com.ciscowebex.androidsdk.WebexError -import com.ciscowebex.androidsdk.annotation.renderer.LiveAnnotationRenderer import com.ciscowebex.androidsdk.kitchensink.BuildConfig import com.ciscowebex.androidsdk.kitchensink.R import com.ciscowebex.androidsdk.kitchensink.WebexRepository import com.ciscowebex.androidsdk.kitchensink.WebexViewModel +import com.ciscowebex.androidsdk.kitchensink.annotation.AnnotationRenderer import com.ciscowebex.androidsdk.kitchensink.auth.LoginActivity import com.ciscowebex.androidsdk.kitchensink.calling.captions.ClosedCaptionsController import com.ciscowebex.androidsdk.kitchensink.calling.captions.ClosedCaptionsViewModel @@ -90,13 +90,16 @@ import com.ciscowebex.androidsdk.phone.Phone import com.ciscowebex.androidsdk.phone.VirtualBackground import com.ciscowebex.androidsdk.phone.Breakout import com.ciscowebex.androidsdk.phone.BreakoutSession +import com.ciscowebex.androidsdk.phone.CompanionMode import com.ciscowebex.androidsdk.phone.ReceivingNoiseInfo import com.ciscowebex.androidsdk.phone.ShareConfig import com.ciscowebex.androidsdk.phone.annotation.LiveAnnotationsPolicy import com.ciscowebex.androidsdk.phone.closedCaptions.CaptionItem import com.ciscowebex.androidsdk.phone.closedCaptions.ClosedCaptionsInfo +import com.ciscowebex.androidsdk.kitchensink.utils.GlobalExceptionHandler import org.koin.android.ext.android.inject import com.ciscowebex.androidsdk.utils.internal.MimeUtils +import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.CoroutineScope import java.io.File import java.util.Date @@ -142,6 +145,7 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface private var isInPipMode = false private var screenShareOptionsDialog: AlertDialog? = null private lateinit var annotationPermissionDialog: AlertDialog + private lateinit var moveMeeting: CompanionMode // Is true when trying to join a Breakout Session, and becomes false when successfully joined or error occurs // Call onDisconnected is fired when user is the last one to leave main session and tries to join a breakout session. @@ -237,7 +241,7 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface binding.ibHoldCall.isSelected = isOnHold ?: false } - private fun getMediaOption(isModerator: Boolean = false, pin: String = "", captcha: String = "", captchaId: String = ""): MediaOption { + private fun getMediaOption(isModerator: Boolean = false, pin: String = "", captcha: String = "", captchaId: String = "", companionMode: CompanionMode = CompanionMode.None): MediaOption { val mediaOption: MediaOption if (webexViewModel.callCapability == WebexRepository.CallCap.Audio_Only) { mediaOption = MediaOption.audioOnly() @@ -249,17 +253,18 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface mediaOption.setPin(pin) mediaOption.setCaptchaCode(captcha) mediaOption.setCaptchaId(captchaId) + mediaOption.setCompanionMode(companionMode) return mediaOption } - fun dialOutgoingCall(callerId: String, isModerator: Boolean = false, pin: String = "", captcha: String = "", captchaId: String = "", isCucmOrWxcCall: Boolean) { + fun dialOutgoingCall(callerId: String, isModerator: Boolean = false, pin: String = "", captcha: String = "", captchaId: String = "", isCucmOrWxcCall: Boolean, moveMeeting: CompanionMode = CompanionMode.None) { Log.d(TAG, "dialOutgoingCall") this.callerId = callerId if(isCucmOrWxcCall) { - webexViewModel.dialPhoneNumber(callerId, getMediaOption(isModerator, pin, captcha, captchaId)) + webexViewModel.dialPhoneNumber(callerId, getMediaOption(isModerator, pin, captcha, captchaId, moveMeeting)) } else { - webexViewModel.dial(callerId, getMediaOption(isModerator, pin, captcha, captchaId)) + webexViewModel.dial(callerId, getMediaOption(isModerator, pin, captcha, captchaId, moveMeeting)) } } @@ -280,7 +285,9 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface "isSelfCreator: ${webexViewModel.isSelfCreator()}, " + "isSpaceMeeting: ${webexViewModel.isSpaceMeeting()}, "+ "isScheduledMeeting: ${webexViewModel.isScheduledMeeting()}") - + // Setting exception handler before making any call. Ideally this would be set in onCallConnected event + // but due to codegen limitation, we are setting it here. + Thread.setDefaultUncaughtExceptionHandler(GlobalExceptionHandler()) onCallConnected(call?.getCallId().orEmpty(), call?.isCUCMCall() ?: false, call?.isWebexCallingOrWebexForBroadworks() ?: false) webexViewModel.sendFeedback(call?.getCallId().orEmpty(), 5, "Testing Comments SDK-v3") webexViewModel.setShareMaxCaptureFPSSetting(30) @@ -318,7 +325,7 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface override fun onDisconnected(call: Call?, event: CallObserver.CallDisconnectedEvent?) { Log.d(TAG, "CallObserver onDisconnected : " + call?.getCallId()) - + Thread.setDefaultUncaughtExceptionHandler(null) var callFailed = false var callEnded = false var localClose = false @@ -349,6 +356,7 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface } is CallObserver.OtherConnected -> { Log.d(TAG, "CallObserver OtherConnected") + callEnded = true } is CallObserver.OtherDeclined -> { Log.d(TAG, "CallObserver OtherDeclined") @@ -808,7 +816,7 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface override fun onClosedCaptionsArrived(captions: CaptionItem) { CoroutineScope(Dispatchers.Main).launch { - captionsController.showCaptionView(binding.rootLayout, captions) + captionsController.showCaptionView(binding.root, captions) if(captions.isFinal) { captionsViewModel.updateData(captions) Log.d(TAG, " Captions are arrived from ${captions.getDisplayName()}") @@ -828,6 +836,10 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface } } + override fun onMoveMeetingFailed(call: Call?) { + showDialogWithMessage(requireContext(), R.string.move_meeting_failed, getString(R.string.move_meeting_failed_message)) + } + @SuppressLint("NotifyDataSetChanged") private fun observerCallLiveData() { @@ -842,7 +854,7 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface if (it) { Log.d(TAG, "startShareLiveData success") // For this share screen session initialize live annotations - webexViewModel.initalizeAnnotations(LiveAnnotationRenderer(requireContext())) + webexViewModel.initalizeAnnotations(AnnotationRenderer(requireContext())) if(BuildConfig.FLAVOR != "wxc") { binding.annotationPolicy.visibility = VISIBLE binding.annotationPolicy.text = webexViewModel.getCurrentLiveAnnotationPolicy().toString() @@ -1208,7 +1220,7 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface dialOutgoingCall(callerId, isHost, pinTitleEditText.text.toString(), captchaInputText.text.toString(), - data?.getId()?:"", false) + data?.getId()?:"", false, moveMeeting) } } } @@ -1479,6 +1491,11 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface {toggleAudioMode(AudioMode.SPEAKER)}, {toggleAudioMode(AudioMode.BLUETOOTH)}, {toggleAudioMode(AudioMode.WIRED_HEADSET)}) callingActivity = bundle?.getInt(Constants.Intent.CALLING_ACTIVITY_ID, 0)!! + moveMeeting = if(bundle.getBoolean(Constants.Intent.MOVE_MEETING, false)) { + CompanionMode.MoveMeeting + } else { + CompanionMode.None + } val incomingCallId = bundle.getString(Constants.Intent.CALL_ID) ?: "" if (callingActivity == 1) { isIncomingActivity = true @@ -2872,10 +2889,10 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface showDialogForHostKey(requireContext(), getString(R.string.enter_host_key), onPositiveButtonClick = { dialog: DialogInterface, number: String -> webexViewModel.reclaimHost(number){ if (it.isSuccessful) { - showToast("Reclaim Host Successful") + showSnackbar("Reclaim Host Successful") Log.d(TAG, "Reclaim Host Successful") } else { - showToast("Reclaim Host failed ${it.error?.errorMessage}") + showSnackbar("Reclaim Host failed ${it.error?.errorMessage}") Log.d(TAG, "Reclaim Host failed ${it.error?.errorMessage}") } } @@ -3242,7 +3259,8 @@ class CallControlsFragment : Fragment(), OnClickListener, CallObserverInterface mediaPlayer.reset() super.onStop() } - private fun showToast(message: String) { - Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show() + + private fun showSnackbar(message: String) { + Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show() } } \ No newline at end of file diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallObserverInterface.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallObserverInterface.kt index de3d588..7487266 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallObserverInterface.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/CallObserverInterface.kt @@ -47,4 +47,5 @@ interface CallObserverInterface { // Closedcaption fun onClosedCaptionsArrived(closedCaptions: CaptionItem) fun onClosedCaptionsInfoChanged(closedCaptionsInfo: ClosedCaptionsInfo) + fun onMoveMeetingFailed(call: Call?) } \ No newline at end of file diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/DialFragment.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/DialFragment.kt index 306d411..0cc4b09 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/DialFragment.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/DialFragment.kt @@ -19,6 +19,7 @@ class DialFragment : Fragment() { lateinit var binding: FragmentCallBinding private var isAddingCall = false private var switchToCucmOrWxcCallToggle = false + private var moveMeeting = false companion object{ private const val IS_ADDING_CALL = "isAddingCall" @@ -67,7 +68,7 @@ class DialFragment : Fragment() { activity?.setResult(Activity.RESULT_OK, intent) activity?.finish() }else{ - startActivity(context?.let { ctx -> CallActivity.getOutgoingIntent(ctx, dialText, switchToCucmOrWxcCallToggle) }) + startActivity(context?.let { ctx -> CallActivity.getOutgoingIntent(ctx, dialText, switchToCucmOrWxcCallToggle, moveMeeting)}) } } @@ -81,6 +82,10 @@ class DialFragment : Fragment() { switchToCucmOrWxcCallToggle = isChecked } + binding.moveMeetingSwitch.setOnCheckedChangeListener { _, isChecked -> + moveMeeting = isChecked + } + binding.ibBackspace.setOnClickListener { var str = binding.etDialInput.text.toString() if (str.isNotEmpty()) { diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingJoinActionBottomSheet.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingJoinActionBottomSheet.kt index 1140947..20791ec 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingJoinActionBottomSheet.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingJoinActionBottomSheet.kt @@ -5,17 +5,19 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.ciscowebex.androidsdk.calendarMeeting.CalendarMeeting +import com.ciscowebex.androidsdk.kitchensink.calling.CallActivity import com.ciscowebex.androidsdk.kitchensink.databinding.BottomSheetCalendarMeetingJoinOptionsBinding import com.google.android.material.bottomsheet.BottomSheetDialogFragment class CalendarMeetingJoinActionBottomSheet( - val joinByMeetingIdClickListener: (String) -> Unit, - val joinByMeetingLinkClickListener: (String) -> Unit, - val joinByMeetingNumberClickListener: (String) -> Unit + val joinByMeetingIdClickListener: (String, Boolean) -> Unit, + val joinByMeetingLinkClickListener: (String, Boolean) -> Unit, + val joinByMeetingNumberClickListener: (String, Boolean) -> Unit ) : BottomSheetDialogFragment() { private lateinit var binding: BottomSheetCalendarMeetingJoinOptionsBinding var meeting : CalendarMeeting? = null + var moveMeeting: Boolean = false override fun onCreateView( inflater: LayoutInflater, @@ -35,17 +37,17 @@ class CalendarMeetingJoinActionBottomSheet( tvJoinByMeetingId.setOnClickListener { dismiss() - joinByMeetingIdClickListener(meeting?.id ?: "") + joinByMeetingIdClickListener(meeting?.id ?: "", moveMeeting) } tvJoinByMeetingLink.setOnClickListener { dismiss() - joinByMeetingLinkClickListener(meeting?.link ?: "") + joinByMeetingLinkClickListener(meeting?.link ?: "", moveMeeting) } tvJoinByMeetingNumber.setOnClickListener { dismiss() - joinByMeetingNumberClickListener(meeting?.sipUrl ?: "") + joinByMeetingNumberClickListener(meeting?.sipUrl ?: "", moveMeeting) } tvCancel.setOnClickListener { dismiss() } diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingListFragment.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingListFragment.kt index 85cda5f..960edd2 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingListFragment.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingListFragment.kt @@ -30,11 +30,11 @@ class CalendarMeetingListFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return FragmentMeetingListBinding.inflate(inflater, container, false).also { binding = it }.apply { val meetingJoinOptionsDialogFragment = CalendarMeetingJoinActionBottomSheet( - {meetingId -> joinByMeetingId(meetingId)}, - {meetingLink -> joinByMeetingLink(meetingLink)}, - {sipUrl -> joinBySipUrl(sipUrl)} + {meetingId, moveMeeting -> joinByMeetingId(meetingId, moveMeeting)}, + {meetingLink, moveMeeting -> joinByMeetingLink(meetingLink, moveMeeting)}, + {sipUrl, moveMeeting -> joinBySipUrl(sipUrl, moveMeeting)} ) - calendarMeetingListAdapter = CalendarMeetingListAdapter(meetingJoinOptionsDialogFragment, requireActivity().supportFragmentManager) { listItemPosition -> + calendarMeetingListAdapter = CalendarMeetingListAdapter(meetingJoinOptionsDialogFragment, requireActivity().supportFragmentManager, meetingsViewModel) { listItemPosition -> context?.let { val meetingItem = calendarMeetingListAdapter.meetings[listItemPosition] it.startActivity(CalendarMeetingDetailsActivity.getIntent(it, meetingItem.calendarMeeting.id ?: "")) @@ -82,6 +82,10 @@ class CalendarMeetingListFragment : Fragment() { meetingsViewModel.onFilterItemClick(CalendarMeetingsViewModel.FilterMeetingsBy.AllMeetings) closeFABMenu() } + binding.tvOngoing.setOnClickListener { + meetingsViewModel.onFilterItemClick(CalendarMeetingsViewModel.FilterMeetingsBy.Ongoing) + closeFABMenu() + } } private fun showFABMenu() { @@ -96,6 +100,8 @@ class CalendarMeetingListFragment : Fragment() { binding.tvToday.animate().translationY(-resources.getDimension(R.dimen.filter_meetings_pos4)) binding.tvAllMeetings.animate().alpha(1F).duration = 250 binding.tvAllMeetings.animate().translationY(-resources.getDimension(R.dimen.filter_meetings_pos5)) + binding.tvOngoing.animate().alpha(1F).duration = 250 + binding.tvOngoing.animate().translationY(-resources.getDimension(R.dimen.filter_meetings_pos6)) } private fun closeFABMenu() { @@ -110,6 +116,8 @@ class CalendarMeetingListFragment : Fragment() { binding.tvToday.animate().alpha(0F).duration = 300 binding.tvAllMeetings.animate().translationY(0F) binding.tvAllMeetings.animate().alpha(0F).duration = 300 + binding.tvOngoing.animate().translationY(0F) + binding.tvOngoing.animate().alpha(0F).duration = 300 } private fun setUpObservers() { @@ -155,28 +163,29 @@ class CalendarMeetingListFragment : Fragment() { }) } - private fun joinByMeetingId(meetingId: String) { + private fun joinByMeetingId(meetingId: String, moveMeeting: Boolean) { context?.let { - startActivity(CallActivity.getOutgoingIntent(it, meetingId, false)) + startActivity(CallActivity.getOutgoingIntent(it, meetingId, false, moveMeeting)) } } - private fun joinByMeetingLink(meetingLink: String) { + private fun joinByMeetingLink(meetingLink: String, moveMeeting: Boolean) { context?.let { - startActivity(CallActivity.getOutgoingIntent(it, meetingLink, false)) + startActivity(CallActivity.getOutgoingIntent(it, meetingLink, false, moveMeeting)) } } - private fun joinBySipUrl(sipUrl: String) { + private fun joinBySipUrl(sipUrl: String, moveMeeting: Boolean) { context?.let { - startActivity(CallActivity.getOutgoingIntent(it, sipUrl, false)) + startActivity(CallActivity.getOutgoingIntent(it, sipUrl, false, moveMeeting)) } } class CalendarMeetingListAdapter( private val meetingJoinOptionsDialogFragment: CalendarMeetingJoinActionBottomSheet, private val supportFragmentManager: FragmentManager, - private val onListItemClicked : (listItemPosition: Int) -> Unit + private val meetingsViewModel: CalendarMeetingsViewModel, + private val onListItemClicked : (listItemPosition: Int) -> Unit, ) : RecyclerView.Adapter() { var meetings: MutableList = mutableListOf() @@ -200,7 +209,7 @@ class CalendarMeetingListFragment : Fragment() { override fun getItemCount(): Int = meetings.size override fun onBindViewHolder(holder: MeetingListViewHolder, position: Int) { - holder.bind(meetings[position]) + holder.bind(meetings[position], meetingsViewModel) } } @@ -208,7 +217,7 @@ class CalendarMeetingListFragment : Fragment() { private val meetingJoinOptionsDialogFragment: CalendarMeetingJoinActionBottomSheet, private val supportFragmentManager: FragmentManager, private val binding: ListItemCalendarMeetingsBinding, - private val onListItemClicked : (listItemPosition: Int) -> Unit + private val onListItemClicked : (listItemPosition: Int) -> Unit, ) : RecyclerView.ViewHolder(binding.root) { init { @@ -217,16 +226,23 @@ class CalendarMeetingListFragment : Fragment() { } } - fun bind(meetingModel: CalendarMeetingModel) { + fun bind(meetingModel: CalendarMeetingModel, meetingsViewModel: CalendarMeetingsViewModel) { binding.meeting = meetingModel.calendarMeeting val currentTime = Date().time - val showJoinButton = (meetingModel.calendarMeeting.startTime?.time ?: 0L <= currentTime && meetingModel.calendarMeeting.endTime?.time ?: 0L >= currentTime) || meetingModel.calendarMeeting.canJoin + val showJoinButton = ((meetingModel.calendarMeeting.startTime?.time ?: 0L) <= currentTime && (meetingModel.calendarMeeting.endTime?.time ?: 0L) >= currentTime) || meetingModel.calendarMeeting.canJoin + val isMoveMeetingPossible = meetingModel.calendarMeeting.isOngoingMeeting && meetingsViewModel.isMoveMeetingSupported(meetingModel.calendarMeeting.id ?: "") binding.btnJoinMeeting.visibility = if (showJoinButton) View.VISIBLE else View.GONE + binding.btnMoveMeeting.visibility = if (isMoveMeetingPossible) View.VISIBLE else View.GONE binding.tvTime.text = meetingModel.date binding.btnJoinMeeting.setOnClickListener { meetingJoinOptionsDialogFragment.meeting = meetingModel.calendarMeeting meetingJoinOptionsDialogFragment.show(supportFragmentManager, "Calendar meeting join options") } + binding.btnMoveMeeting.setOnClickListener { + meetingJoinOptionsDialogFragment.meeting = meetingModel.calendarMeeting + meetingJoinOptionsDialogFragment.moveMeeting = true + meetingJoinOptionsDialogFragment.show(supportFragmentManager, "Calendar meeting join options") + } binding.executePendingBindings() } } diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingsRepository.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingsRepository.kt index de709c8..e88f833 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingsRepository.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingsRepository.kt @@ -31,4 +31,8 @@ class CalendarMeetingsRepository(private val webex: Webex) { }) }.toObservable() } + + fun isMoveMeetingSupported(meetingId: String): Boolean { + return webex.calendarMeetings.isMoveMeetingSupported(meetingId) + } } \ No newline at end of file diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingsViewModel.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingsViewModel.kt index 8cada80..076a543 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingsViewModel.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/calendarMeeting/CalendarMeetingsViewModel.kt @@ -26,13 +26,22 @@ class CalendarMeetingsViewModel(private val repo: CalendarMeetingsRepository, pr fun getCalendarMeetingEvent() = webexRepository._calendarMeetingEventLiveData - fun getCalendarMeetingsList(fromDate: Date? = null, toDate: Date? = null) { + fun getCalendarMeetingsList(fromDate: Date? = null, toDate: Date? = null, isOngoing: Boolean = false) { repo.listCalendarMeetings(fromDate, toDate).observeOn(AndroidSchedulers.mainThread()) .subscribe({ meetingsList -> + if(isOngoing) { + val ongoingMeetings = meetingsList.filter { it.isOngoingMeeting } + _meetings.postValue(ongoingMeetings) + return@subscribe + } _meetings.postValue(meetingsList) }, { _meetings.postValue(emptyList()) }).autoDispose() } + fun isMoveMeetingSupported(meetingId: String): Boolean { + return repo.isMoveMeetingSupported(meetingId) + } + fun onFilterItemClick(filterByOption: FilterMeetingsBy) { when (filterByOption) { FilterMeetingsBy.Today -> { @@ -50,6 +59,7 @@ class CalendarMeetingsViewModel(private val repo: CalendarMeetingsRepository, pr FilterMeetingsBy.UpcomingMeetings -> { getCalendarMeetingsList(Date()) } + FilterMeetingsBy.Ongoing -> { getCalendarMeetingsList(null, null, true) } FilterMeetingsBy.AllMeetings -> { getCalendarMeetingsList() } @@ -61,6 +71,7 @@ class CalendarMeetingsViewModel(private val repo: CalendarMeetingsRepository, pr Tomorrow, PastMeetings, UpcomingMeetings, - AllMeetings + AllMeetings, + Ongoing } } diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/captions/ClosedCaptionsController.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/captions/ClosedCaptionsController.kt index d3cb52b..6de044e 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/captions/ClosedCaptionsController.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/captions/ClosedCaptionsController.kt @@ -227,13 +227,15 @@ class ClosedCaptionsController(var call: Call?) { snackbar = null } }) + snackbar?.show() } - val textView = snackbar?.view?.findViewById(com.google.android.material.R.id.snackbar_text) - textView?.maxLines = 5 - textView?.text = "${caption.getDisplayName()} : ${caption.getContent().toString()}" - if (caption.isFinal) textView?.text = "${textView?.text} \n" - snackbar?.show() + if (snackbar?.isShown == true) { + val textView = snackbar?.view?.findViewById(com.google.android.material.R.id.snackbar_text) + textView?.maxLines = 5 + textView?.text = "${caption.getDisplayName()} : ${caption.getContent().toString()}" + if (caption.isFinal) textView?.text = "${textView?.text} \n" + } } fun restCaptionView() { diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/participants/ParticipantsFragment.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/participants/ParticipantsFragment.kt index b1039b4..2ba2442 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/participants/ParticipantsFragment.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/participants/ParticipantsFragment.kt @@ -22,6 +22,7 @@ import com.ciscowebex.androidsdk.kitchensink.utils.showDialogWithMessage import com.ciscowebex.androidsdk.phone.MakeHostError import com.ciscowebex.androidsdk.phone.InviteParticipantError import com.ciscowebex.androidsdk.phone.CallMembership +import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_participants.* @@ -116,7 +117,7 @@ class ParticipantsFragment : DialogFragment(), ParticipantsAdapter.OnItemActionL webexViewModel.muteAllParticipantAudio(it) } } else { - showToast(getString(R.string.mute_feature_is_not_available_for_cucm_calls)) + showSnackbar(getString(R.string.mute_feature_is_not_available_for_cucm_calls)) } } binding.inviteParticipantButton.setOnClickListener { @@ -127,8 +128,8 @@ class ParticipantsFragment : DialogFragment(), ParticipantsAdapter.OnItemActionL } - private fun showToast(message: String) { - Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show() + private fun showSnackbar(message: String) { + Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show() } override fun onParticipantMuted(participantId: String, hasPairedParticipant: Boolean) { @@ -137,10 +138,10 @@ class ParticipantsFragment : DialogFragment(), ParticipantsAdapter.OnItemActionL webexViewModel.muteParticipant(it, participantId) adapter.notifyDataSetChanged() } else { - showToast(getString(R.string.mute_feature_is_not_available_for_cucm_calls)) + showSnackbar(getString(R.string.mute_feature_is_not_available_for_cucm_calls)) } if(hasPairedParticipant) { - showToast(getString(R.string.mute_feature_paired_participant)) + showSnackbar(getString(R.string.mute_feature_paired_participant)) } } } @@ -166,10 +167,10 @@ class ParticipantsFragment : DialogFragment(), ParticipantsAdapter.OnItemActionL showDialogForTextBox(requireContext(), getString(R.string.invite_participant), onPositiveButtonClick = { dialog: DialogInterface, invitee: String -> webexViewModel.inviteParticipant(invitee) { if (it.isSuccessful) { - showToast("Invite Participant Successful") + showSnackbar("Invite Participant Successful") Log.d(tag, "Invite Participant Successful") } else { - showToast("Invite Participant failed ${it.error?.errorMessage}") + showSnackbar("Invite Participant failed ${it.error?.errorMessage}") Log.d(tag, "Invite Participant failed ${it.error?.errorMessage}") } } @@ -187,9 +188,9 @@ class ParticipantsFragment : DialogFragment(), ParticipantsAdapter.OnItemActionL currentCallId?.let { webexViewModel.makeHost(participantId) { if (it.isSuccessful) { - showToast(getString(R.string.assign_host_success)) + showSnackbar(getString(R.string.assign_host_success)) } else { - showToast(it.error?.errorMessage ?: getString(R.string.assign_host_failure)) + showSnackbar(it.error?.errorMessage ?: getString(R.string.assign_host_failure)) } } diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/MessagingRepository.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/MessagingRepository.kt index 04c64f3..454fa1a 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/MessagingRepository.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/MessagingRepository.kt @@ -82,6 +82,19 @@ open class MessagingRepository(private val webex: Webex) { }.toObservable() } + fun getMessageWithoutObserver(messageId: String): SpaceMessageModel? { + var message: SpaceMessageModel? = null + webex.messages.get(messageId, CompletionHandler { result -> + message = if (result.isSuccessful) { + (SpaceMessageModel.convertToSpaceMessageModel(result.data)) + } else { + null + } + + }) + return message + } + fun editMessage(messageId: String, messageText: Message.Text, mentions: ArrayList?): Observable { return Single.create { emitter -> webex.messages.get(messageId, CompletionHandler { messageResult -> diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/spaces/detail/MessageViewModel.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/spaces/detail/MessageViewModel.kt index e5f1807..9f7cc0a 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/spaces/detail/MessageViewModel.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/spaces/detail/MessageViewModel.kt @@ -38,6 +38,10 @@ class MessageViewModel(private val spaceRepo: SpacesRepository) : BaseViewModel( }, { error -> _error.postValue(error.message) }).autoDispose() } + fun getMessage(messageId: String): SpaceMessageModel? { + return spaceRepo.getMessageWithoutObserver(messageId) + } + fun downloadThumbnail(remoteFile: RemoteFile, file: File) { spaceRepo.downloadThumbnail(remoteFile, file).observeOn(AndroidSchedulers.mainThread()).subscribe({ _uri.postValue(it) diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/spaces/detail/SpaceDetailActivity.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/spaces/detail/SpaceDetailActivity.kt index 6389bc0..73321e2 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/spaces/detail/SpaceDetailActivity.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/spaces/detail/SpaceDetailActivity.kt @@ -41,6 +41,7 @@ class SpaceDetailActivity : BaseActivity() { lateinit var binding: ActivitySpaceDetailBinding private val spaceDetailViewModel: SpaceDetailViewModel by inject() + private val messageViewModel: MessageViewModel by inject() private lateinit var spaceId: String override fun onCreate(savedInstanceState: Bundle?) { @@ -75,19 +76,9 @@ class SpaceDetailActivity : BaseActivity() { } private fun replyMessageListener(message: SpaceMessageModel) { - val model = ReplyMessageModel( - message.spaceId, - message.messageId, - message.created, - message.isSelfMentioned, - message.parentId, - message.isReply, - message.personId, - message.personEmail, - message.toPersonId, - message.toPersonEmail) - ContextCompat.startActivity(this@SpaceDetailActivity, - MessageComposerActivity.getIntent(this@SpaceDetailActivity, MessageComposerActivity.Companion.ComposerType.POST_SPACE, spaceDetailViewModel.spaceId, model), null) + val parentMessage = messageViewModel.getMessage(message.parentId) + val model = parentMessage?.let { ReplyMessageModel(it.spaceId, it.messageId, it.created, it.isSelfMentioned, it.parentId, it.isReply, it.personId, it.personEmail, it.toPersonId, it.toPersonEmail) } + startActivity(MessageComposerActivity.getIntent(this@SpaceDetailActivity, MessageComposerActivity.Companion.ComposerType.POST_SPACE, spaceDetailViewModel.spaceId, model), null) } private fun editMessage(message: SpaceMessageModel) { diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/SearchCommonFragment.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/SearchCommonFragment.kt index ca30b13..723553a 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/SearchCommonFragment.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/SearchCommonFragment.kt @@ -327,7 +327,7 @@ class SearchCommonFragment : Fragment() { else binding.image.visibility = View.VISIBLE binding.image.setOnClickListener { - it.context.startActivity(CallActivity.getOutgoingIntent(it.context, itemModel.callerId, itemModel.isPhoneNumber)) + it.context.startActivity(CallActivity.getOutgoingIntent(it.context, itemModel.callerId, itemModel.isPhoneNumber, false)) } if (itemModel.ongoing) { diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/CallObjectStorage.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/CallObjectStorage.kt index 043913c..d03f11e 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/CallObjectStorage.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/CallObjectStorage.kt @@ -57,6 +57,15 @@ object CallObjectStorage { } } + fun getCallObjectFromIndex(index: Int): Call? { + synchronized(this) { + if (index >= 0 && index < callObjects.size) { + return callObjects[index] + } + return null + } + } + fun clearStorage() { synchronized(this) { callObjects.clear() diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/Constants.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/Constants.kt index 8f47568..b4bc1b8 100644 --- a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/Constants.kt +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/Constants.kt @@ -20,6 +20,7 @@ class Constants { const val REJECT_REQUEST_CODE = 1002 const val FULLSCREEN_REQUEST_CODE = 1001 const val CALL_TYPE = "SwitchCallType" + const val MOVE_MEETING = "MoveMeeting" const val CLOSED_CAPTION_DATA = "closed_captions_data" const val CLOSED_CAPTION_LANGUAGES = "closed_captions_languages" const val CLOSED_CAPTION_LANGUAGE_ITEM = "closed_captions_language_item" diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/GlobalExceptionHandler.kt b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/GlobalExceptionHandler.kt new file mode 100644 index 0000000..4778878 --- /dev/null +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/GlobalExceptionHandler.kt @@ -0,0 +1,54 @@ +package com.ciscowebex.androidsdk.kitchensink.utils + +import android.util.Log +import com.ciscowebex.androidsdk.phone.Call +import com.ciscowebex.androidsdk.phone.CallObserver +import com.ciscowebex.androidsdk.kitchensink.utils.CallObjectStorage +import kotlin.system.exitProcess + +internal class GlobalExceptionHandler : Thread.UncaughtExceptionHandler { + private val tag = "GlobalExceptionHandler" + override fun uncaughtException(thread: Thread, throwable: Throwable) { + Log.e(tag, "Uncaught exception", throwable) + Log.d(tag, "Size of call object storage : ${CallObjectStorage.size()}") + // Get the call that is in connected state inside the CallObjectStorage and hangup the call + var call: Call? = null + + for (i in 0 until CallObjectStorage.size()) { + val callObj = CallObjectStorage.getCallObjectFromIndex(i) + if (callObj?.getStatus() == Call.CallStatus.CONNECTED) { + Log.d(tag, "Call with id = ${callObj.getCallId()} found in connected state. ") + call = callObj + break + } + } + CallObjectStorage.clearStorage() + + if (call == null) { + Log.d(tag, "No call found in connected state. Proceeding to kill app!") + killApp() + } + call?.setObserver(object : CallObserver { + override fun onDisconnected(event: CallObserver.CallDisconnectedEvent?) { + Log.d(tag, "Call disconnected fired, killing app!") + killApp() + } + }) + call?.hangup {result -> + if (result.isSuccessful) { + Log.d(tag, "Call hung up success") + } else { + Log.e(tag, "Call hangup failed, reason : ${result.error?.errorMessage}") + killApp() + } + } ?: run { + Log.d(tag, "Crash detected & no active call found. Proceeding to kill app!") + killApp() + } + } + + private fun killApp() { + android.os.Process.killProcess(android.os.Process.myPid()) + exitProcess(1) + } +} diff --git a/app/src/main/res/layout/fragment_call.xml b/app/src/main/res/layout/fragment_call.xml index 895939a..7d60afd 100644 --- a/app/src/main/res/layout/fragment_call.xml +++ b/app/src/main/res/layout/fragment_call.xml @@ -250,29 +250,36 @@ android:inputType="textWebEmailAddress" app:layout_constraintEnd_toStartOf="@+id/ib_backspace" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/toggle_buttons_container" /> + app:layout_constraintTop_toBottomOf="@id/moveMeetingSwitch" /> + app:layout_constraintTop_toTopOf="@id/et_dial_input" + app:layout_constraintBottom_toBottomOf="@+id/et_dial_input"/> + + + + + android:paddingEnd="@dimen/size_8dp" + android:paddingBottom="@dimen/size_8dp"> @@ -39,10 +39,24 @@ android:backgroundTint="@color/green" android:text="@string/call_meeting_join" android:visibility="gone" - tools:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + tools:visibility="visible" /> + +