From 793b23128ab351426fb6bf5c6b17cceca1de233f Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Fri, 5 Jul 2024 11:18:30 +0530 Subject: [PATCH 1/2] 3.12.0 Release changes --- README.md | 6 +-- app/build.gradle | 10 ++-- .../teams/membership/TeamFragmentTest.kt | 14 ++--- .../annotation/AnnotationRenderer.kt | 32 +++++++++++ app/src/full/res/values/strings.xml | 4 ++ .../kitchensink/calling/CallActivity.kt | 11 +++- .../calling/CallObserverInterface.kt | 1 + .../kitchensink/calling/DialFragment.kt | 7 ++- .../CalendarMeetingJoinActionBottomSheet.kt | 14 ++--- .../CalendarMeetingListFragment.kt | 46 ++++++++++------ .../CalendarMeetingsRepository.kt | 4 ++ .../CalendarMeetingsViewModel.kt | 15 +++++- .../captions/ClosedCaptionsController.kt | 12 +++-- .../participants/ParticipantsFragment.kt | 19 +++---- .../spaces/detail/MessageViewModel.kt | 4 ++ .../spaces/detail/SpaceDetailActivity.kt | 17 ++---- .../search/SearchCommonFragment.kt | 2 +- .../kitchensink/utils/CallObjectStorage.kt | 9 ++++ .../utils/GlobalExceptionHandler.kt | 54 +++++++++++++++++++ app/src/main/res/layout/fragment_call.xml | 25 +++++---- .../main/res/layout/fragment_meeting_list.xml | 13 +++++ .../layout/list_item_calendar_meetings.xml | 24 +++++++-- app/src/main/res/values/dimens.xml | 1 + .../annotation/AnnotationRenderer.kt | 32 +++++++++++ .../annotation/AnnotationRenderer.kt | 31 +++++++++++ 25 files changed, 324 insertions(+), 83 deletions(-) create mode 100644 app/src/full/java/com/ciscowebex/androidsdk/kitchensink/annotation/AnnotationRenderer.kt create mode 100644 app/src/full/res/values/strings.xml create mode 100644 app/src/main/java/com/ciscowebex/androidsdk/kitchensink/utils/GlobalExceptionHandler.kt create mode 100644 app/src/meeting/java/com/ciscowebex/androidsdk/kitchensink/annotation/AnnotationRenderer.kt create mode 100644 app/src/wxc/java/com/ciscowebex/androidsdk/kitchensink/annotation/AnnotationRenderer.kt 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/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/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/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/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" /> + +