working on videoCall notification

video-stream-crash_fixes
mosazaid 4 years ago
parent 529711d2ce
commit e06bd6a4f8

@ -7,6 +7,6 @@ class AppApplication : FlutterApplication() {
override fun onCreate() {
super.onCreate()
LoggingExceptionHandler(this, "ErrorFile")
// LoggingExceptionHandler(this, "ErrorFile")
}
}

@ -6,6 +6,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
@ -15,14 +16,10 @@ import android.util.Log
import android.widget.Toast
import androidx.annotation.NonNull
import com.google.gson.GsonBuilder
import com.hmg.hmgDr.Service.VideoStreamFloatingWidgetService
import com.hmg.hmgDr.model.GetSessionStatusModel
import com.hmg.hmgDr.model.SessionStatusModel
import com.hmg.hmgDr.Service.VideoStreamFloatingWidgetService
import com.hmg.hmgDr.globalErrorHandler.FileUtil
import com.hmg.hmgDr.globalErrorHandler.LoggingExceptionHandler
import com.hmg.hmgDr.ui.VideoCallResponseListener
import com.hmg.hmgDr.ui.fragment.VideoCallFragment
import com.opentok.android.Session
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
@ -134,11 +131,7 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
}
private fun checkFloatingWidgetPermission() {
requestPermissions()
//Check if the application has draw over other apps permission or not?
//This permission is by default available for API<23. But for API > 23
//you have to ask for the permission in runtime.
// requestPermissions()
// Check if the application has draw over other apps permission or not?
// This permission is by default available for API<23. But for API > 23
@ -288,7 +281,6 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
private fun unbindService() {
if (bound) {
videoStreamService?.videoCallResponseListener = null // unregister
// videoStreamService!!.mActivity = null
unbindService(serviceConnection)
bound = false
}
@ -304,7 +296,6 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
bound = true
videoStreamService!!.videoCallResponseListener = this@MainActivity // register
videoStreamService?.serviceRunning = true
// videoStreamService!!.mActivity = this@MainActivity // register
}
override fun onServiceDisconnected(name: ComponentName?) {
@ -319,8 +310,14 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
if (EasyPermissions.hasPermissions(this, *perms)) {
} else {
var havePermission = false
for (requiredPermission: String in perms) {
val checkVal: Int = checkCallingOrSelfPermission(requiredPermission)
if (checkVal == PackageManager.PERMISSION_GRANTED) {
havePermission = true
}
}
if (!havePermission) {
EasyPermissions.requestPermissions(
this,
getString(R.string.remaining_ar),
@ -328,6 +325,15 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
*perms
)
}
/* if (EasyPermissions.hasPermissions(this, *perms)) {
} else {
EasyPermissions.requestPermissions(
this,
getString(R.string.remaining_ar),
RC_READ_WRITE,
*perms
)
}*/
}
override fun onPermissionsGranted(requestCode: Int, perms: MutableList<String>?) {

@ -17,16 +17,19 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.view.GestureDetectorCompat
import com.hmg.hmgDr.MainActivity
import com.hmg.hmgDr.R
import com.hmg.hmgDr.model.*
import com.hmg.hmgDr.ui.VideoCallContract
import com.hmg.hmgDr.ui.VideoCallPresenterImpl
import com.hmg.hmgDr.ui.VideoCallResponseListener
import com.hmg.hmgDr.util.DateUtils
import com.hmg.hmgDr.util.DynamicVideoRenderer
import com.hmg.hmgDr.util.NotificationUtil
import com.hmg.hmgDr.util.ViewsUtil
import com.hmg.hmgDr.util.audio.CustomAudioDevice
import com.opentok.android.*
import java.util.*
import kotlin.math.ceil
@ -53,8 +56,18 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
const val ACTION_START_CALL = "com.hmg.hmgDr.Service.action.startCall"
const val ACTION_MINIMIZE_CALL = "com.hmg.hmgDr.Service.action.minimizeCall"
const val ACTION_END_CALL = "com.hmg.hmgDr.Service.action.endCall"
const val ACTION_CHANGE_EXPANDED_COLLAPSED =
"com.hmg.hmgDr.Service.action.changeExpandedCollapsed"
const val NOTIFICATION_IS_EXPANDED = "is_expanded"
}
var arguments: Bundle? = null
var serviceRunning: Boolean = false
private val serviceBinder: IBinder = VideoStreamBinder()
private lateinit var videoCallPresenter: VideoCallContract.VideoCallPresenter
var videoCallResponseListener: VideoCallResponseListener? = null
private lateinit var windowManagerParams: WindowManager.LayoutParams
private var mWindowManager: WindowManager? = null
private val szWindow = Point()
@ -86,10 +99,8 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
private lateinit var mspeckerBtn: ImageView
private lateinit var mMicBtn: ImageView
private lateinit var patientName: TextView
private lateinit var cmTimer: Chronometer
private lateinit var videoCallPresenter: VideoCallContract.VideoCallPresenter
var videoCallResponseListener: VideoCallResponseListener? = null
private lateinit var tvTimer: TextView
// private lateinit var cmTimer: Chronometer
private var mSession: Session? = null
private var mPublisher: Publisher? = null
@ -101,6 +112,10 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
private var mConnectedRunnable: Runnable? = null
private var isConnected = false
private var mCallTimerHandler: Handler? = null
private var mCallTimerRunnable: Runnable? = null
private var mCallFirstTime: Date = Date()
private var sessionStatusModel: GetSessionStatusModel? = null
private lateinit var mDetector: GestureDetectorCompat
@ -119,30 +134,15 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
private var formattedCallTime: String = "00:00"
private lateinit var notificationData: NotificationVideoModel
private val mVideoPLRatio = 0.0
private val mVideoBw: Long = 0
private val mAudioPLRatio = 0.0
private val mAudioBw: Long = 0
private val mPrevVideoPacketsLost: Long = 0
private val mPrevVideoPacketsRcvd: Long = 0
private val mPrevVideoTimestamp = 0.0
private val mPrevVideoBytes: Long = 0
private val mPrevAudioPacketsLost: Long = 0
private val mPrevAudioPacketsRcvd: Long = 0
private val mPrevAudioTimestamp = 0.0
private val mPrevAudioBytes: Long = 0
private val mStartTestTime: Long = 0
private val audioOnly = false
private var resume = false
var isFullScreen: Boolean = true
private var isCircle: Boolean = false
lateinit var customAudioDevice: CustomAudioDevice
var arguments: Bundle? = null
var serviceRunning: Boolean = false
private val serviceBinder: IBinder = VideoStreamBinder()
// notification's layout
private lateinit var mRemoteViews: RemoteViews
private lateinit var mRemoteViewsExpand: RemoteViews
// Notification variables
private var mNotificationManagerCompat: NotificationManagerCompat? = null
@ -189,6 +189,26 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
} else if (action == ACTION_MINIMIZE_CALL) {
if (!isFullScreen)
onMinimizedClicked()
} else if (action == ACTION_CHANGE_EXPANDED_COLLAPSED) {
val isExpanded = intent.extras!!.getBoolean(NOTIFICATION_IS_EXPANDED)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// No use channel importance
if (isExpanded) {
notificationCompatBuilder.priority = Notification.PRIORITY_LOW
} else {
notificationCompatBuilder.priority = Notification.PRIORITY_HIGH
}
} else {
if (isExpanded) {
notificationCompatBuilder.priority = Notification.PRIORITY_LOW
} else {
notificationCompatBuilder.priority = Notification.PRIORITY_HIGH
}
}
mNotificationManagerCompat?.notify(
ONGOING_NOTIFICATION_ID,
notificationCompatBuilder.build()
)
}
}
@ -216,9 +236,18 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
}
private fun stopService() {
// customAudioDevice.setRendererMute(false)
AudioDeviceManager.getAudioDevice().onPause()
// cmTimer.stop()
mCallTimerRunnable?.let {
mCallTimerHandler?.removeCallbacks(it)
mCallTimerHandler = null
mCallTimerRunnable = null
}
// because problem that timer still continue longer a bit than service so notification called again
mNotificationManagerCompat = null
cmTimer.stop()
stopForeground(true)
stopSelf()
videoCallResponseListener?.minimizeVideoEvent(false)
@ -302,10 +331,10 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
recordContainer.visibility = View.GONE
}
/*
cmTimer.format = "mm:ss"
cmTimer.onChronometerTickListener =
Chronometer.OnChronometerTickListener { arg0: Chronometer? ->
Chronometer.OnChronometerTickListener { arg0: Chronometer ->
// val f: NumberFormat = DecimalFormat("00")
// f.format(minutes)
val minutes: Long
@ -321,38 +350,14 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
}
val format = "%1$02d:%2$02d" // two digits
formattedCallTime = String.format(format, minutes, seconds)
arg0?.text = formattedCallTime
try {
notificationCompatBuilder.contentView.setChronometer(
R.id.notify_timer,
cmTimer.base,
null,
true
)
notificationCompatBuilder.bigContentView.setChronometer(
R.id.notify_timer,
cmTimer.base,
null,
true
)
} catch (e: Exception) {
}
// for change notification timer
if (mNotificationManagerCompat != null) {
val bigTextStyle = setNotificationBigStyle()
notificationCompatBuilder.setStyle(bigTextStyle)
mNotificationManagerCompat?.notify(
ONGOING_NOTIFICATION_ID,
notificationCompatBuilder.build()
)
}
arg0.text = formattedCallTime
}
*/
videoCallPresenter = VideoCallPresenterImpl(this, baseUrl)
checkClientConnected()
setCallStartTimer()
handleVideoViewHeight()
mDetector = GestureDetectorCompat(
@ -363,18 +368,17 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
)
try {
// Add a custom audio device before session initialization
// Add a custom audio device before session initialization
val customAudioDevice = CustomAudioDevice(
this
)
customAudioDevice.setRendererMute(true)
customAudioDevice = CustomAudioDevice(this)
customAudioDevice.setRendererMute(false)
if (AudioDeviceManager.getAudioDevice() == null) {
AudioDeviceManager.setAudioDevice(customAudioDevice)
} else {
AudioDeviceManager.getAudioDevice().onResume()
}
mSession = Session.Builder(this, apiKey, sessionId).build()
mSession!!.setSessionListener(this)
mSession!!.connect(token)
mSession!!.setReconnectionListener(object : Session.ReconnectionListener {
override fun onReconnecting(session: Session?) {
@ -406,7 +410,7 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
mSubscriberViewContainer = view.findViewById(R.id.remote_video_view_container)
patientName = view.findViewById(R.id.patient_name)
cmTimer = view.findViewById(R.id.cmTimer)
tvTimer = view.findViewById(R.id.tv_timer)
controlPanel = view.findViewById(R.id.control_panel)
mCallBtn = view.findViewById(R.id.btn_call)
@ -430,6 +434,45 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
)
}
private fun setCallStartTimer() {
mCallTimerHandler = Handler((Looper.getMainLooper()))
mCallTimerRunnable = Runnable {
formattedCallTime = DateUtils.differentDateTimeBetweenDateAndNow(mCallFirstTime)
tvTimer.text = formattedCallTime
/*
// comment it because I deleted remoteView
try {
notificationCompatBuilder.contentView.setChronometer(
R.id.notify_timer,
cmTimer.base,
null,
true
)
notificationCompatBuilder.bigContentView.setChronometer(
R.id.notify_timer,
cmTimer.base,
null,
true
)
} catch (e: Exception) {
print(e)
}*/
// for change notification timer
if (mNotificationManagerCompat != null) {
val bigTextStyle = setNotificationBigStyle()
notificationCompatBuilder.setStyle(bigTextStyle)
mNotificationManagerCompat?.notify(
ONGOING_NOTIFICATION_ID,
notificationCompatBuilder.build()
)
}
mCallTimerHandler!!.postDelayed(mCallTimerRunnable!!, 1000L)
}
}
private fun handleVideoViewHeight() {
val layoutNameParam: ConstraintLayout.LayoutParams =
layoutName.layoutParams as ConstraintLayout.LayoutParams
@ -698,7 +741,7 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
isMicClicked = !isMicClicked
mPublisher!!.publishAudio = !isMicClicked
val res = if (isMicClicked) R.drawable.mic_disabled else R.drawable.mic_enabled
mMicBtn!!.setImageResource(res)
mMicBtn.setImageResource(res)
}
}
@ -796,10 +839,10 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
mSession!!.publish(mPublisher)
if (!resume) {
cmTimer.base = SystemClock.elapsedRealtime()
}
cmTimer.start()
// start call timer to show on top
mCallFirstTime = Date()
mCallTimerHandler!!.postDelayed(mCallTimerRunnable!!, 1000L)
videoCallResponseListener?.minimizeVideoEvent(true)
}
@ -823,6 +866,7 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
TAG,
"onStreamReceived: New stream " + stream.streamId + " in session " + session.sessionId
)
if (mSubscriber != null) {
isConnected = true
return
@ -1111,24 +1155,57 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
)
}
// notification's layout
val mRemoteViews = RemoteViews(packageName, R.layout.notifi_video_view)
/*
mRemoteViews = RemoteViews(packageName, R.layout.notifi_video_view)
mRemoteViewsExpand = RemoteViews(packageName, R.layout.notifi_video_view_expand)
val changeCollapsedIntent: PendingIntent =
Intent(this, VideoStreamFloatingWidgetService::class.java).apply {
action = ACTION_CHANGE_EXPANDED_COLLAPSED
val extras = Bundle()
extras.putBoolean(NOTIFICATION_IS_EXPANDED, false)
putExtras(extras)
}
.let { notificationIntent ->
PendingIntent.getService(this, 0, notificationIntent, 0)
}
val changeExpandedIntent: PendingIntent =
Intent(this, VideoStreamFloatingWidgetService::class.java).apply {
action = ACTION_CHANGE_EXPANDED_COLLAPSED
val extras = Bundle()
extras.putBoolean(NOTIFICATION_IS_EXPANDED, true)
putExtras(extras)
}
.let { notificationIntent ->
PendingIntent.getService(this, 0, notificationIntent, 0)
}
mRemoteViews.setImageViewResource(R.id.iv_icon, R.mipmap.ic_launcher)
// notification's title
mRemoteViews.setTextViewText(R.id.notify_title, notificationData.mContentTitle)
// notification's content
mRemoteViews.setTextViewText(R.id.notify_content, notificationData.mContentText)
val mRemoteViewsExpand = RemoteViews(packageName, R.layout.notifi_video_view_expand)
mRemoteViews.setOnClickPendingIntent(R.id.iv_Arrow, changeCollapsedIntent)
mRemoteViews.setChronometer(
R.id.notify_timer,
SystemClock.elapsedRealtime(),
null,
false
)
mRemoteViewsExpand.setImageViewResource(R.id.iv_icon, R.mipmap.ic_launcher)
mRemoteViewsExpand.setTextViewText(R.id.notify_title, notificationData.mContentTitle)
mRemoteViewsExpand.setTextViewText(R.id.notify_content, notificationData.mContentText)
mRemoteViewsExpand.setOnClickPendingIntent(R.id.btn_end, endCallPendingIntent)
mRemoteViewsExpand.setOnClickPendingIntent(R.id.iv_Arrow, changeExpandedIntent)
mRemoteViewsExpand.setChronometer(
R.id.notify_timer,
SystemClock.elapsedRealtime(),
null,
false
)
*/
notificationCompatBuilder
// BIG_TEXT_STYLE sets title and content for API 16 (4.1 and after).
@ -1144,8 +1221,8 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
R.mipmap.ic_launcher
)
)
.setCustomContentView(mRemoteViews)
.setCustomBigContentView(mRemoteViewsExpand)
/*.setCustomContentView(mRemoteViews)
.setCustomBigContentView(mRemoteViewsExpand)*/
.setContentIntent(pendingIntent)
// .setDefaults(NotificationCompat.DEFAULT_ALL)
// Set primary color (important for Wear 2.0 Notifications).
@ -1153,11 +1230,15 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
.setCategory(Notification.CATEGORY_SERVICE)
.setPriority(notificationData.mPriority)
.setVisibility(notificationData.mChannelLockscreenVisibility)
// .addAction(endCallAction)
.addAction(endCallAction) // for version code < O, or for all versions if RemoteViews not used
.setUsesChronometer(true)
.setWhen(elapsedTime)
.setOnlyAlertOnce(true) // This will ensure that updates to an existing notification won't sound/vibrate https://stackoverflow.com/a/52349466/6246772
.setSound(null)
.setOngoing(true)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setColorized(true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) //to show content in lock screen
val notification = notificationCompatBuilder
.build()
@ -1236,11 +1317,6 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
private val dragListener: View.OnTouchListener = View.OnTouchListener { _, event ->
mDetector.onTouchEvent(event)
if (floatingWidgetView!!.layoutParams is WindowManager.LayoutParams) {
var asd = "";
}
//Get Floating widget view params
val layoutParams: WindowManager.LayoutParams =
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
@ -1382,6 +1458,4 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
}
}.start()
}
}

@ -1,7 +0,0 @@
package com.hmg.hmgDr.errorhandler
/**
* A functional interface representing an action that gets executed
* upon an error by an [ErrorHandler].
*/
typealias Action = (Throwable, ErrorHandler) -> Unit

@ -1,34 +0,0 @@
package com.hmg.hmgDr.errorhandler
/**
* Container to ease passing around a tuple of two objects. This object provides a sensible
* implementation of equals(), returning true if equals() is true on each of the contained
* objects.
*/
class ActionEntry
/**
* Constructor for an ActionEntry.
*
* @param matcher the matcher object in the ActionEntry
* @param action the action object in the ActionEntry
*/(
val matcher: Matcher,
val action: Action
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || javaClass != other.javaClass) return false
val that = other as ActionEntry
return if (matcher != that.matcher) false else action == that.action
}
/**
* Compute a hash code using the hash codes of the underlying objects
*
* @return a hashcode of the ActionEntry
*/
override fun hashCode(): Int {
return matcher.hashCode() xor action.hashCode()
}
}

@ -1,505 +0,0 @@
package com.hmg.hmgDr.errorhandler
import java.util.HashMap
import kotlin.reflect.KClass
/**
* https://github.com/mgray88/kotlin-error-handler
* An ErrorHandler is responsible for handling an error by executing one or more actions,
* instances of [Action], that are found to match the error.
*/
class ErrorHandler private constructor() {
private val errorCodeMap = mutableMapOf<ErrorCodeIdentifier<*>, MatcherFactory<*>>()
private val actions = mutableListOf<ActionEntry>()
private val otherwiseActions= mutableListOf<Action>()
private val alwaysActions = mutableListOf<Action>()
private var localContext: ThreadLocal<Context> = object : ThreadLocal<Context>() {
override fun initialValue(): Context {
return Context()
}
}
private var parentErrorHandler: ErrorHandler? = null
/**
* Create a new ErrorHandler with the given one as parent.
*
* @param parentErrorHandler the parent @{link ErrorHandler}
*/
private constructor(parentErrorHandler: ErrorHandler) : this() {
this.parentErrorHandler = parentErrorHandler
}
/**
* Register `action` to be executed by [.handle],
* if the thrown error matches the `matcher`.
*
* @param matcher a matcher to match the thrown error
* @param action the associated action
* @return the current `ErrorHandler` instance - to use in command chains
*/
fun on(
matcher: Matcher,
action: Action
): ErrorHandler {
actions.add(ActionEntry(matcher, action))
return this
}
/**
* Kotlin <1.4 lambda compatibility for `[.on(Matcher, Action)]`
*/
fun on(
matcher: (Throwable) -> Boolean,
action: Action
): ErrorHandler {
return on(object : Matcher {
override fun matches(throwable: Throwable): Boolean {
return matcher(throwable)
}
}, action)
}
/**
* Register `action` to be executed by [.handle],
* if the thrown error is an instance of `exceptionClass`.
*
* @param exceptionClass the class of the error
* @param action the associated action
* @return the current `ErrorHandler` instance - to use in command chains
*/
fun on(
exceptionClass: KClass<out Throwable>,
action: Action
): ErrorHandler {
actions.add(ActionEntry(ExceptionMatcher(exceptionClass), action))
return this
}
/**
* Register `action` to be executed by [.handle],
* if the thrown error is bound (associated) to `errorCode`.
*
*
* See [.bindClass] and [.bind]
* on how to associate arbitrary error codes with actual Throwables via [Matcher].
*
* @param <T> the error code type
* @param errorCode the error code
* @param action the associated action
* @return the current `ErrorHandler` instance - to use in command chains
</T> */
fun <T : Any> on(
errorCode: T,
action: Action
): ErrorHandler {
val matcherFactory: MatcherFactory<T> =
getMatcherFactoryForErrorCode(errorCode)
?: throw UnknownErrorCodeException(errorCode)
actions.add(ActionEntry(matcherFactory.build(errorCode), action))
return this
}
/**
* Register `action` to be executed in case no other *conditional*
* action gets executed.
*
* @param action the action
* @return the current `ErrorHandler` instance - to use in command chains
*/
fun otherwise(action: Action): ErrorHandler {
otherwiseActions.add(action)
return this
}
/**
* Register `action` to be executed on all errors.
*
* @param action the action
* @return the current `ErrorHandler` instance - to use in command chains
*/
fun always(action: Action): ErrorHandler {
alwaysActions.add(action)
return this
}
/**
* Skip all following actions registered via an `on` method
* @return the current `ErrorHandler` instance - to use in command chains
*/
fun skipFollowing(): ErrorHandler {
localContext.get().skipFollowing = true
return this
}
/**
* Skip all actions registered via [.always]
* @return the current `ErrorHandler` instance - to use in command chains
*/
fun skipAlways(): ErrorHandler {
localContext.get().skipAlways = true
return this
}
/**
* Skip the default matching actions if any
* @return the current `ErrorHandler` instance - to use in command chains
*/
fun skipDefaults(): ErrorHandler {
localContext.get().skipDefaults = true
return this
}
private fun handle(
error: Throwable,
context: ThreadLocal<Context>
) {
localContext = context
val ctx = localContext.get()
for (actionEntry in actions) {
if (ctx.skipFollowing) break
if (actionEntry.matcher.matches(error)) {
actionEntry.action(error, this)
ctx.handled = true
}
}
if (!ctx.handled && otherwiseActions.isNotEmpty()) {
for (action in otherwiseActions) {
action(error, this)
ctx.handled = true
}
}
if (!ctx.skipAlways) {
for (action in alwaysActions) {
action(error, this)
ctx.handled = true
}
}
if (!ctx.skipDefaults) {
parentErrorHandler?.handle(error, localContext)
}
}
/**
* Run a custom code block and assign current ErrorHandler instance
* to handle a possible exception throw in 'catch'.
*
* @param closure functional interface containing Exception prone code
*/
fun runHandling(closure: () -> Unit) {
try {
closure()
} catch (throwable: Throwable) {
handle(throwable, localContext)
}
}
/**
* Handle `error` by executing all matching actions.
*
* @param error the error as a [Throwable]
*/
fun handle(error: Throwable) {
this.handle(error, localContext)
}
/**
* Bind an `errorCode` to a `Matcher`, using a `MatcherFactory`.
*
*
*
* For example, when we need to catch a network timeout it's better to just write "timeout"
* instead of a train-wreck expression. So we need to bind this "timeout" error code to an actual
* condition that will check the actual error when it occurs to see if its a network timeout or not.
*
*
* <pre>
* ```
* ErrorHandler
* .defaultErrorHandler()
* .bind("timeout") { errorCode ->
* Matcher { throwable ->
* return (throwable is SocketTimeoutException) && throwable.message.contains("Read timed out")
* }
* }
*
* // ...
*
* ErrorHandler
* .create()
* .on("timeout") { throwable, handler ->
* showOfflineScreen()
* }
* ```
* </pre>
*
*
* @param <T> the error code type
* @param errorCode the errorCode value, can use a primitive for clarity and let it be autoboxed
* @param matcherFactory a factory that given an error code, provides a matcher to match the error against it
* @return the current `ErrorHandler` instance - to use in command chains
*/
fun <T : Any> bind(
errorCode: T,
matcherFactory: MatcherFactory<T>
): ErrorHandler {
errorCodeMap[ErrorCodeIdentifier(errorCode)] = matcherFactory
return this
}
/**
* Kotlin <1.4 lambda compatibility for `[.bind(T, MatcherFactory<T>)]`
*/
fun <T : Any> bind(
errorCode: T,
matcherFactory: (T) -> (Throwable) -> Boolean
): ErrorHandler {
return bind(errorCode, object : MatcherFactory<T> {
override fun build(errorCode: T): Matcher {
val matcher = matcherFactory(errorCode)
return object : Matcher {
override fun matches(throwable: Throwable): Boolean {
return matcher(throwable)
}
}
}
})
}
/**
* Bind an `errorCode` `Class` to a `Matcher`, using a `MatcherFactory`.
*
*
*
* For example, when we prefer using plain integers to refer to HTTP errors instead of
* checking the HTTPException status code every time.
*
*
* <pre>
* ```
* ErrorHandler
* .defaultErrorHandler()
* .bindClass(Integer::class) { errorCode ->
* Matcher { throwable ->
* return throwable is HttpException && throwable.code() == errorCode
* }
* }
*
* // ...
*
* ErrorHandler
* .create()
* .on(404) { throwable, handler ->
* showResourceNotFoundError()
* }
* .on(500) { throwable, handler ->
* showServerError()
* }
* ````
* </pre>
*
* @param <T> the error code type
* @param errorCodeClass the errorCode class
* @param matcherFactory a factory that given an error code, provides a matcher to match the error against it
* @return the current `ErrorHandler` instance - to use in command chains
*/
fun <T : Any> bindClass(
errorCodeClass: KClass<T>,
matcherFactory: MatcherFactory<T>
): ErrorHandler {
errorCodeMap[ErrorCodeIdentifier(errorCodeClass)] = matcherFactory
return this
}
/**
* Kotlin <1.4 lambda compatibility for `[.bindClass(KClass<T>, MatcherFactory<T>)]`
*/
fun <T : Any> bindClass(
errorCodeClass: KClass<T>,
matcherFactory: (T) -> (Throwable) -> Boolean
): ErrorHandler {
return bindClass(errorCodeClass, object : MatcherFactory<T> {
override fun build(errorCode: T): Matcher {
val matcher = matcherFactory(errorCode)
return object : Matcher {
override fun matches(throwable: Throwable): Boolean {
return matcher(throwable)
}
}
}
})
}
@Suppress("UNCHECKED_CAST")
protected fun <T : Any> getMatcherFactoryForErrorCode(errorCode: T): MatcherFactory<T>? {
var matcherFactory: MatcherFactory<T>?
matcherFactory = errorCodeMap[ErrorCodeIdentifier(errorCode)] as? MatcherFactory<T>
if (matcherFactory != null) {
return matcherFactory
}
matcherFactory = errorCodeMap[ErrorCodeIdentifier(errorCode::class)] as? MatcherFactory<T>
if (matcherFactory != null) {
return matcherFactory
}
return if (parentErrorHandler != null) {
parentErrorHandler?.getMatcherFactoryForErrorCode(errorCode)
} else null
}
/**
* Clear ErrorHandler instance from all its registered Actions and Matchers.
*/
fun clear() {
actions.clear()
errorCodeMap.clear()
otherwiseActions.clear()
alwaysActions.clear()
localContext.get().clear()
}
private class Context {
private val keys = HashMap<String, Any>()
var handled = false
var skipDefaults = false
var skipFollowing = false
var skipAlways = false
operator fun get(key: Any?): Any? {
return keys[key]
}
fun put(key: String, value: Any): Any? {
return keys.put(key, value)
}
fun remove(key: Any?): Any? {
return keys.remove(key)
}
fun clear() {
keys.clear()
skipDefaults = false
skipFollowing = false
skipAlways = false
}
}
/**
* Used to identify an error code either by its "literal" value
* or by its Class.
*
*
* When using custom objects as error codes,
* make sure you implement [Object.equals] to allow ErrorHandler
* perform equality comparisons between instances.
*/
private class ErrorCodeIdentifier<out T : Any> {
private val errorCode: T?
private val errorCodeClass: KClass<out T>?
internal constructor(errorCode: T) {
this.errorCode = errorCode
this.errorCodeClass = null
}
internal constructor(errorCodeClass: KClass<out T>) {
this.errorCode = null
this.errorCodeClass = errorCodeClass
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || javaClass != other.javaClass) return false
val that = other as ErrorCodeIdentifier<*>
if (if (errorCode != null) errorCode != that.errorCode else that.errorCode != null) return false
return if (errorCodeClass != null) errorCodeClass == that.errorCodeClass else that.errorCodeClass == null
}
override fun hashCode(): Int {
var result = errorCode?.hashCode() ?: 0
result = 31 * result + (errorCodeClass?.hashCode() ?: 0)
return result
}
}
companion object {
private var defaultInstance: ErrorHandler? = null
/**
* Create a new @{link ErrorHandler}, isolated from the default one.
*
*
* In other words, designed to handle all errors by itself without delegating
* to the default error handler.
*
* @return returns a new `ErrorHandler` instance
*/
@JvmStatic
fun createIsolated(): ErrorHandler {
return ErrorHandler()
}
/**
* Create a new @{link ErrorHandler}, that delegates to the default one, or the
* parent @{link ErrorHandler} passed in
*
* Any default actions, are always executed after the ones registered on this one.
*
* @param parentErrorHandler `ErrorHandler` to use as the parent
* @return returns a new `ErrorHandler` instance
*/
@JvmStatic
fun create(parentErrorHandler: ErrorHandler? = null): ErrorHandler {
return ErrorHandler(parentErrorHandler ?: defaultErrorHandler())
}
/**
* Get the default @{link ErrorHandler}, a singleton object
* to which all other instances by default delegate to.
*
* @return the default @{link ErrorHandler} instance
*/
@JvmStatic
@Synchronized
fun defaultErrorHandler(): ErrorHandler {
if (defaultInstance == null) {
defaultInstance =
ErrorHandler()
}
return defaultInstance!!
}
}
}
/**
* Wrapper around `[.on(KClass, Action)]` to allow action's `Throwable` parameter
* to be typed to the `Throwable` expected
*/
inline fun <reified T : Throwable> ErrorHandler.on(
noinline action: (T, ErrorHandler) -> Unit
): ErrorHandler {
return on(T::class) { throwable, errorHandler ->
action(throwable as T, errorHandler)
}
}
/**
* Lazy `ErrorHandler` initializer which delegates to a parent, or the `defaultErrorHandler`
* if the parent is not supplied. Uses optional lambda function to add actions and bindings to
* the new `ErrorHandler`
*
* @param parentErrorHandler (optional) error handler to delegate default actions to
* @param apply (optional) apply function for adding actions and binding
* @return lazy initialized `ErrorHandler`
*/
inline fun errorHandler(
parentErrorHandler: ErrorHandler? = null,
noinline apply: (ErrorHandler.() -> Unit)? = null
) = lazy {
val eh = ErrorHandler.create(parentErrorHandler)
apply?.invoke(eh)
return@lazy eh
}

@ -1,9 +0,0 @@
package com.hmg.hmgDr.errorhandler
import kotlin.reflect.KClass
class ExceptionMatcher(private val errorClass: KClass<out Throwable>) : Matcher {
override fun matches(throwable: Throwable): Boolean {
return errorClass.isInstance(throwable)
}
}

@ -1,5 +0,0 @@
package com.hmg.hmgDr.errorhandler
interface Matcher {
fun matches(throwable: Throwable): Boolean
}

@ -1,11 +0,0 @@
package com.hmg.hmgDr.errorhandler
interface MatcherFactory<in T : Any> {
/**
* Build a [Matcher] to match the given error code against an error
*
* @param errorCode the error code
* @return a new [Matcher]
*/
fun build(errorCode: T): Matcher
}

@ -1,3 +0,0 @@
package com.hmg.hmgDr.errorhandler
class UnknownErrorCodeException(val errorCode: Any) : RuntimeException()

@ -1,45 +0,0 @@
package com.hmg.hmgDr.errorhandler.retrofit
/**
* Range class for HTTP status codes
*/
class Range private constructor(val lowerBound: Int, val upperBound: Int) {
/**
* Checks if the passed httpStatusCode is contained in given range
*
* @param httpStatusCode the status code to check
* @return true if contains, otherwise false
*/
operator fun contains(httpStatusCode: Int): Boolean {
return httpStatusCode in lowerBound..upperBound
}
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o == null || javaClass != o.javaClass) return false
val range =
o as Range
return if (lowerBound != range.lowerBound) false else upperBound == range.upperBound
}
override fun hashCode(): Int {
var result = lowerBound
result = 31 * result + upperBound
return result
}
companion object {
/**
* Creates a Range object with lower and upper bound
* @param lowerBound lower limit of Range
* @param upperBound upper limit of Range
*
* @return a Range instance
*/
@JvmStatic
fun of(lowerBound: Int, upperBound: Int): Range {
return Range(lowerBound, upperBound)
}
}
}

@ -1,48 +0,0 @@
package com.hmg.hmgDr.errorhandler.retrofit
import com.hmg.hmgDr.errorhandler.Matcher
import com.hmg.hmgDr.errorhandler.MatcherFactory
import retrofit2.adapter.rxjava.HttpException
object RetrofitMatcherFactory {
/**
* Creates a [MatcherFactory] that checks HTTP statuses
*
* @return new MatcherFactory for Retrofit Rx HttpException that works with Integer
*/
@JvmStatic
fun create(): MatcherFactory<Int> {
return object : MatcherFactory<Int> {
override fun build(errorCode: Int): Matcher {
return object : Matcher {
override fun matches(throwable: Throwable): Boolean {
return throwable is HttpException &&
throwable.code() == errorCode
}
}
}
}
}
/**
* Creates a [MatcherFactory] that checks if HTTP status is in given [Range]
*
* @return new MatcherFactory for Retrofit Rx HttpException that works with Range
*/
@JvmStatic
fun createRange(): MatcherFactory<Range> {
return object : MatcherFactory<Range> {
override fun build(errorCode: Range): Matcher {
return object : Matcher {
override fun matches(throwable: Throwable): Boolean {
return throwable is HttpException &&
errorCode.contains(throwable.code())
}
}
}
}
}
}

@ -32,6 +32,8 @@ object FileUtil {
bufferedWriter.flush()
} catch (e: IOException) {
e.printStackTrace()
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}
}

@ -1,5 +1,6 @@
package com.hmg.hmgDr.model
import android.app.Notification
import android.app.NotificationManager
import android.os.Build
import androidx.annotation.RequiresApi
@ -11,7 +12,7 @@ class NotificationVideoModel constructor(
mChannelId: String,
mChannelName: CharSequence,
mChannelDescription: String,
mPriority: Int = NotificationCompat.PRIORITY_DEFAULT,
mPriority: Int = Notification.PRIORITY_MAX,
mChannelImportance: Int = NotificationManager.IMPORTANCE_LOW,
mChannelEnableVibrate: Boolean = true,
mChannelLockscreenVisibility: Int = NotificationCompat.VISIBILITY_PUBLIC,
@ -19,7 +20,6 @@ class NotificationVideoModel constructor(
var mBigContentTitle: String = mContentTitle,
val mBigText: String = mContentText,
var mSummaryText: String
) : NotificationDataModel(
mContentTitle,
mContentText,

@ -294,7 +294,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session
recordContainer.visibility = View.GONE
}
cmTimer = view.findViewById(R.id.cmTimer)
// cmTimer = view.findViewById(R.id.cmTimer)
cmTimer.format = "mm:ss"
cmTimer.onChronometerTickListener =
Chronometer.OnChronometerTickListener { arg0: Chronometer? ->

@ -0,0 +1,38 @@
package com.hmg.hmgDr.util
import java.text.SimpleDateFormat
import java.util.*
object DateUtils {
var simpleDateFormat: SimpleDateFormat = SimpleDateFormat("hh:mm:ss", Locale.ENGLISH)
fun differentDateTimeBetweenDateAndNow(firstDate: Date): String {
val now: Date = Calendar.getInstance().time
//1 minute = 60 seconds
//1 hour = 60 x 60 = 3600
//1 day = 3600 x 24 = 86400
var different: Long = now.time - firstDate.time
val secondsInMilli: Long = 1000
val minutesInMilli = secondsInMilli * 60
val hoursInMilli = minutesInMilli * 60
val daysInMilli = hoursInMilli * 24
val elapsedDays = different / daysInMilli
different %= daysInMilli
val elapsedHours = different / hoursInMilli
different %= hoursInMilli
val elapsedMinutes = different / minutesInMilli
different %= minutesInMilli
val elapsedSeconds = different / secondsInMilli
val format = "%1$02d:%2$02d" // two digits
return String.format(format, elapsedMinutes, elapsedSeconds)
}
}

@ -9,7 +9,10 @@ import com.hmg.hmgDr.model.NotificationDataModel
object NotificationUtil {
fun createNotificationChannel(context: Context, notificationDataModel: NotificationDataModel): String{
fun createNotificationChannel(
context: Context,
notificationDataModel: NotificationDataModel
): String {
// The id of the channel.
val channelId: String = notificationDataModel.mChannelId
@ -21,7 +24,8 @@ object NotificationUtil {
val channelDescription: String = notificationDataModel.mChannelDescription
val channelImportance: Int = notificationDataModel.mChannelImportance
val channelEnableVibrate: Boolean = notificationDataModel.mChannelEnableVibrate
val channelLockscreenVisibility: Int = notificationDataModel.mChannelLockscreenVisibility
val channelLockscreenVisibility: Int =
notificationDataModel.mChannelLockscreenVisibility
// Initializes NotificationChannel.
val notificationChannel = NotificationChannel(channelId, channelName, channelImportance)

@ -263,7 +263,7 @@ class CustomAudioDevice(context: Context) : BaseAudioDevice() {
return false
}
m_bufferedPlaySamples = 0
setOutputMode(OutputMode.SpeakerPhone)
outputMode = OutputMode.SpeakerPhone
m_shutdownRenderThread = false
Thread(m_renderThread).start()
return true

@ -33,15 +33,25 @@
android:background="@drawable/shape_capsule"
android:padding="@dimen/padding_space_small">
<Chronometer
android:id="@+id/cmTimer"
<TextView
android:id="@+id/tv_timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="@color/white"
android:textSize="16sp"
android:textSize="@dimen/text_size_medium"
android:textStyle="bold"
tools:text="25:45" />
android:text="00:00" />
<!-- <Chronometer-->
<!-- android:id="@+id/cmTimer"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:padding="4dp"-->
<!-- android:textColor="@color/white"-->
<!-- android:textSize="16sp"-->
<!-- android:textStyle="bold"-->
<!-- tools:text="25:45" />-->
</FrameLayout>

@ -46,6 +46,7 @@
tools:text="25:45" />
<ImageView
android:id="@+id/iv_Arrow"
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@drawable/ic_arrow_bottom" />

@ -46,6 +46,7 @@
tools:text="25:45" />
<ImageView
android:id="@+id/iv_Arrow"
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@drawable/ic_arrow_top" />

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<style name="LaunchTheme" parent="Theme.AppCompat.NoActionBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>

Loading…
Cancel
Save