video stream crash working to fix

video-stream-floating
mosazaid 5 years ago
parent 8a4dcf2334
commit 529711d2ce

@ -108,6 +108,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
}
apply plugin: 'com.google.gms.google-services'

@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.hmg.hmgDr">
<!--
io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here.
-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.CALL_PHONE" />
<!-- Permission required to draw floating widget over other apps -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<application
android:name="io.flutter.app.FlutterApplication"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="HMG Doctor">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- <service android:name = ".Service.VideoStreamContainerService"-->
<!-- android:enabled="true"-->
<!-- android:exported="false"/>-->
<service android:name = ".Service.VideoStreamFloatingWidgetService"
android:enabled="true"
android:exported="false"/>
<!--
Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java
-->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

@ -15,20 +15,25 @@
<!-- Permission required to draw floating widget over other apps -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<application
android:name="io.flutter.app.FlutterApplication"
android:name="AppApplication"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
tools:replace="android:name"
android:label="HMG Doctor">
<activity
android:name=".MainActivity"
@ -44,10 +49,6 @@
</intent-filter>
</activity>
<!-- <service android:name = ".Service.VideoStreamContainerService"-->
<!-- android:enabled="true"-->
<!-- android:exported="false"/>-->
<service android:name = ".Service.VideoStreamFloatingWidgetService"
android:enabled="true"
android:exported="false"/>
@ -59,6 +60,22 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<activity
android:name="com.hmg.hmgDr.globalErrorHandler.UCEDefaultActivity"
android:process=":error_activity" />
<provider
android:name="com.hmg.hmgDr.globalErrorHandler.UCEFileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>

@ -0,0 +1,12 @@
package com.hmg.hmgDr
import com.hmg.hmgDr.globalErrorHandler.LoggingExceptionHandler
import io.flutter.app.FlutterApplication
class AppApplication : FlutterApplication() {
override fun onCreate() {
super.onCreate()
LoggingExceptionHandler(this, "ErrorFile")
}
}

@ -1,5 +1,6 @@
package com.hmg.hmgDr
import android.Manifest
import android.app.Activity
import android.content.ComponentName
import android.content.Context
@ -17,16 +18,23 @@ import com.google.gson.GsonBuilder
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
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import pub.devrel.easypermissions.AfterPermissionGranted
import pub.devrel.easypermissions.AppSettingsDialog
import pub.devrel.easypermissions.EasyPermissions
class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
VideoCallResponseListener {
VideoCallResponseListener, EasyPermissions.PermissionCallbacks {
/* Permission request code to draw over other apps */
private val DRAW_OVER_OTHER_APP_PERMISSION_REQUEST_CODE = 1222
@ -41,6 +49,10 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
private var videoStreamService: VideoStreamFloatingWidgetService? = null
private var bound = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
@ -111,7 +123,7 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
// start service
// serviceIntent = Intent(this@MainActivity, VideoStreamContainerService::class.java)
if (videoStreamService == null || videoStreamService?.serviceRunning == false){
if (videoStreamService == null || videoStreamService?.serviceRunning == false) {
serviceIntent = Intent(this@MainActivity, VideoStreamFloatingWidgetService::class.java)
serviceIntent?.run {
putExtras(arguments)
@ -122,6 +134,8 @@ 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.
@ -177,16 +191,20 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
}
} else*/ if (requestCode == DRAW_OVER_OTHER_APP_PERMISSION_REQUEST_CODE) {
//Check if the permission is granted or not.
if (resultCode == RESULT_OK)
//If permission granted start floating widget service
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(this)) {
startFloatingWidgetService()
} else {
//Permission is not available then display toast
Toast.makeText(
this,
"Draw over other app permission not available. App won\\'t work without permission. Please try again.",
Toast.LENGTH_SHORT
).show()
}
} else {
startFloatingWidgetService()
else
//Permission is not available then display toast
Toast.makeText(
this,
"Draw over other app permission not available. App won\\'t work without permission. Please try again.",
Toast.LENGTH_SHORT
).show()
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
@ -294,4 +312,52 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
}
}
@AfterPermissionGranted(RC_READ_WRITE)
private fun requestPermissions() {
val perms = arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
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>?) {
}
override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) {
if (EasyPermissions.somePermissionPermanentlyDenied(this@MainActivity, perms)) {
AppSettingsDialog.Builder(this)
.setTitle(getString(R.string.title_settings_dialog))
.setRationale(getString(R.string.rationale_ask_again))
.setPositiveButton(getString(R.string.setting))
.setNegativeButton(getString(R.string.cancel))
.setRequestCode(RC_SETTINGS_SCREEN_PERM)
.build()
.show()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String?>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
}
companion object{
private const val RC_READ_WRITE = 1
private const val RC_SETTINGS_SCREEN_PERM = 123
}
}

@ -25,6 +25,7 @@ import com.hmg.hmgDr.ui.VideoCallResponseListener
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 kotlin.math.ceil
@ -45,6 +46,9 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
const val CHANNEL_DEFAULT_NAME = "Video cAll"
const val ONGOING_NOTIFICATION_ID = 1
private const val TEST_DURATION = 20 //test quality duration in seconds
private const val TIME_WINDOW = 3 //3 seconds
private const val TIME_VIDEO_TEST = 15 //time interval to check the video quality in seconds
const val ACTION_START_CALL = "com.hmg.hmgDr.Service.action.startCall"
const val ACTION_MINIMIZE_CALL = "com.hmg.hmgDr.Service.action.minimizeCall"
@ -115,6 +119,22 @@ 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
@ -184,6 +204,9 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
override fun onDestroy() {
try {
mWindowManager?.removeView(floatingWidgetView)
floatingWidgetView = null
mSession = null
mWindowManager = null
} catch (e: Exception) {
Log.e("onDestroyService", "${e.localizedMessage}.")
@ -339,6 +362,15 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
{ miniCircleDoubleTap() })
)
try {
// Add a custom audio device before session initialization
// Add a custom audio device before session initialization
val customAudioDevice = CustomAudioDevice(
this
)
customAudioDevice.setRendererMute(true)
AudioDeviceManager.setAudioDevice(customAudioDevice)
mSession = Session.Builder(this, apiKey, sessionId).build()
mSession!!.setSessionListener(this)
mSession!!.connect(token)
@ -755,6 +787,7 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
// .renderer(ThumbnailCircleVideoRenderer(requireContext()))
.build()
mPublisher!!.setPublisherListener(this)
mPublisher!!.audioFallbackEnabled = false
if (mPublisher!!.view is GLSurfaceView) {
(mPublisher!!.view as GLSurfaceView).setZOrderOnTop(true)
}
@ -833,7 +866,6 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
}
if (mSubscriber != null) {
mSubscriber!!.subscribeToAudio = false // mosa added for error that was originally registered here. Are you missing a call to unregisterReceiver()?
mSubscriberViewContainer.removeView(mSubscriber!!.view)
mSession!!.unsubscribe(mSubscriber)
mSubscriber!!.destroy()
@ -891,6 +923,9 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
*/
override fun onVideoDataReceived(subscriberKit: SubscriberKit?) {
if (mSubscriber == null) {
return
}
mSubscriber!!.setStyle(
BaseVideoRenderer.STYLE_VIDEO_SCALE,
BaseVideoRenderer.STYLE_VIDEO_FILL
@ -972,7 +1007,7 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
}
}
fun onCallChangeCallStatusSuccessful(){
fun onCallChangeCallStatusSuccessful() {
val returnIntent = Intent()
returnIntent.putExtra("CallEnd", sessionStatusModel)
videoCallResponseListener?.onCallFinished(Activity.RESULT_CANCELED, returnIntent)
@ -1232,13 +1267,13 @@ class VideoStreamFloatingWidgetService : Service(), Session.SessionListener,
y_cord_Destination = y_init_margin + y_diff
val barHeight: Int = getStatusBarHeight()
if (y_cord_Destination < 0) {
// y_cord_Destination = 0
y_cord_Destination = 0
// y_cord_Destination =
// -(szWindow.y - (videoCallContainer.height /*+ barHeight*/))
y_cord_Destination = -(szWindow.y / 2)
// y_cord_Destination = -(szWindow.y / 2)
} else if (y_cord_Destination + (videoCallContainer.height + barHeight) > szWindow.y) {
// y_cord_Destination = szWindow.y - (videoCallContainer.height + barHeight)
y_cord_Destination = (szWindow.y / 2)
y_cord_Destination = szWindow.y - (videoCallContainer.height + barHeight)
// y_cord_Destination = (szWindow.y / 2)
}
layoutParams.y = y_cord_Destination

@ -0,0 +1,7 @@
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

@ -0,0 +1,34 @@
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()
}
}

@ -0,0 +1,505 @@
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
}

@ -0,0 +1,9 @@
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)
}
}

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

@ -0,0 +1,11 @@
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
}

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

@ -0,0 +1,45 @@
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)
}
}
}

@ -0,0 +1,48 @@
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())
}
}
}
}
}
}

@ -0,0 +1,37 @@
package com.hmg.hmgDr.globalErrorHandler
import android.os.Environment
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
object FileUtil {
val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
fun pushLog(body: String?) {
try {
val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
val time = SimpleDateFormat("HH:MM:SS", Locale.getDefault()).format(Date())
val root =
File(Environment.getExternalStorageDirectory(),"error_log_dir")
// if external memory exists and folder with name Notes
if (!root.exists()) {
root.mkdirs() // this will create folder.
}
val oldFile = File(root, "error" + sdf.format(Date()).toString() + ".txt") // old file
if (oldFile.exists()) oldFile.delete()
val filepath = File(root, "error$date.txt") // file path to save
val bufferedWriter = BufferedWriter(FileWriter(filepath, true))
bufferedWriter.append("\r\n")
bufferedWriter.append("\r\n").append(body).append(" Time : ").append(time)
bufferedWriter.flush()
} catch (e: IOException) {
e.printStackTrace()
}
}
}

@ -0,0 +1,39 @@
package com.hmg.hmgDr.globalErrorHandler
import android.content.Context
import android.content.Intent
import com.hmg.hmgDr.MainActivity
import com.hmg.hmgDr.globalErrorHandler.FileUtil.pushLog
class LoggingExceptionHandler(private val context: Context, ErrorFile: String) :
Thread.UncaughtExceptionHandler {
private val rootHandler: Thread.UncaughtExceptionHandler
override fun uncaughtException(t: Thread, e: Throwable) {
object : Thread() {
override fun run() {
pushLog("UnCaught Exception is thrown in $error$e")
try {
sleep(500)
val intent = Intent(context, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
context.startActivity(intent)
} catch (e1: Exception) {
e1.printStackTrace()
}
}
}.start()
rootHandler.uncaughtException(t, e)
}
companion object {
private val TAG = LoggingExceptionHandler::class.java.simpleName
lateinit var error: String
}
init {
error = "$ErrorFile.error "
rootHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(this)
}
}

@ -0,0 +1,6 @@
package com.hmg.hmgDr.globalErrorHandler
import androidx.appcompat.app.AppCompatActivity
class UCEDefaultActivity : AppCompatActivity() {
}

@ -0,0 +1,6 @@
package com.hmg.hmgDr.globalErrorHandler
import androidx.core.content.FileProvider
class UCEFileProvider : FileProvider() {
}

@ -0,0 +1,280 @@
package com.hmg.hmgDr.globalErrorHandler
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.Date;
import java.util.Deque;
import java.util.Locale;
import kotlin.system.exitProcess
class UCEHandler(val builder: Builder) {
val EXTRA_STACK_TRACE = "EXTRA_STACK_TRACE"
val EXTRA_ACTIVITY_LOG = "EXTRA_ACTIVITY_LOG"
private val TAG = "UCEHandler"
private val UCE_HANDLER_PACKAGE_NAME = "com.rohitss.uceh"
private val DEFAULT_HANDLER_PACKAGE_NAME = "com.android.internal.os"
private val MAX_STACK_TRACE_SIZE = 131071 //128 KB - 1
private val MAX_ACTIVITIES_IN_LOG = 50
private val SHARED_PREFERENCES_FILE = "uceh_preferences"
private val SHARED_PREFERENCES_FIELD_TIMESTAMP = "last_crash_timestamp"
private val activityLog: Deque<String> = ArrayDeque(MAX_ACTIVITIES_IN_LOG)
var COMMA_SEPARATED_EMAIL_ADDRESSES: String? = null
@SuppressLint("StaticFieldLeak")
private var application: Application? = null
private var isInBackground = true
private var isBackgroundMode = false
private var isUCEHEnabled = false
private var isTrackActivitiesEnabled = false
private var lastActivityCreated: WeakReference<Activity?> = WeakReference(null)
fun UCEHandler(builder: Builder) {
isUCEHEnabled = builder.isUCEHEnabled
isTrackActivitiesEnabled = builder.isTrackActivitiesEnabled
isBackgroundMode = builder.isBackgroundModeEnabled
COMMA_SEPARATED_EMAIL_ADDRESSES = builder.commaSeparatedEmailAddresses
setUCEHandler(builder.context)
}
private fun setUCEHandler(context: Context?) {
try {
if (context != null) {
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
if (oldHandler != null && oldHandler.javaClass.name.startsWith(
UCE_HANDLER_PACKAGE_NAME
)
) {
Log.e(TAG, "UCEHandler was already installed, doing nothing!")
} else {
if (oldHandler != null && !oldHandler.javaClass.name.startsWith(
DEFAULT_HANDLER_PACKAGE_NAME
)
) {
Log.e(
TAG,
"You already have an UncaughtExceptionHandler. If you use a custom UncaughtExceptionHandler, it should be initialized after UCEHandler! Installing anyway, but your original handler will not be called."
)
}
application = context.getApplicationContext() as Application
//Setup UCE Handler.
Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler { thread, throwable ->
if (isUCEHEnabled) {
Log.e(
TAG,
"App crashed, executing UCEHandler's UncaughtExceptionHandler",
throwable
)
if (hasCrashedInTheLastSeconds(application!!)) {
Log.e(
TAG,
"App already crashed recently, not starting custom error activity because we could enter a restart loop. Are you sure that your app does not crash directly on init?",
throwable
)
if (oldHandler != null) {
oldHandler.uncaughtException(thread, throwable)
return@UncaughtExceptionHandler
}
} else {
setLastCrashTimestamp(application!!, Date().getTime())
if (!isInBackground || isBackgroundMode) {
val intent = Intent(application, UCEDefaultActivity::class.java)
val sw = StringWriter()
val pw = PrintWriter(sw)
throwable.printStackTrace(pw)
var stackTraceString: String = sw.toString()
if (stackTraceString.length > MAX_STACK_TRACE_SIZE) {
val disclaimer = " [stack trace too large]"
stackTraceString = stackTraceString.substring(
0,
MAX_STACK_TRACE_SIZE - disclaimer.length
) + disclaimer
}
intent.putExtra(EXTRA_STACK_TRACE, stackTraceString)
if (isTrackActivitiesEnabled) {
val activityLogStringBuilder = StringBuilder()
while (!activityLog.isEmpty()) {
activityLogStringBuilder.append(activityLog.poll())
}
intent.putExtra(
EXTRA_ACTIVITY_LOG,
activityLogStringBuilder.toString()
)
}
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
application!!.startActivity(intent)
} else {
if (oldHandler != null) {
oldHandler.uncaughtException(thread, throwable)
return@UncaughtExceptionHandler
}
//If it is null (should not be), we let it continue and kill the process or it will be stuck
}
}
val lastActivity: Activity? = lastActivityCreated.get()
if (lastActivity != null) {
lastActivity.finish()
lastActivityCreated.clear()
}
killCurrentProcess()
} else oldHandler?.uncaughtException(thread, throwable)
})
application!!.registerActivityLifecycleCallbacks(object :
Application.ActivityLifecycleCallbacks {
val dateFormat: DateFormat =
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
var currentlyStartedActivities = 0
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (activity.javaClass !== UCEDefaultActivity::class.java) {
lastActivityCreated = WeakReference(activity)
}
if (isTrackActivitiesEnabled) {
activityLog.add(
dateFormat.format(Date())
.toString() + ": " + activity.javaClass
.getSimpleName() + " created\n"
)
}
}
override fun onActivityStarted(activity: Activity) {
currentlyStartedActivities++
isInBackground = currentlyStartedActivities == 0
}
override fun onActivityResumed(activity: Activity) {
if (isTrackActivitiesEnabled) {
activityLog.add(
dateFormat.format(Date())
.toString() + ": " + activity.javaClass
.simpleName + " resumed\n"
)
}
}
override fun onActivityPaused(activity: Activity) {
if (isTrackActivitiesEnabled) {
activityLog.add(
dateFormat.format(Date())
.toString() + ": " + activity.javaClass
.simpleName + " paused\n"
)
}
}
override fun onActivityStopped(activity: Activity) {
currentlyStartedActivities--
isInBackground = currentlyStartedActivities == 0
}
override fun onActivitySaveInstanceState(
activity: Activity,
outState: Bundle
) {}
override fun onActivityDestroyed(activity: Activity) {
if (isTrackActivitiesEnabled) {
activityLog.add(
dateFormat.format(Date())
.toString() + ": " + activity.javaClass
.simpleName + " destroyed\n"
)
}
}
})
}
Log.i(TAG, "UCEHandler has been installed.")
} else {
Log.e(TAG, "Context can not be null")
}
} catch (throwable: Throwable) {
Log.e(
TAG,
"UCEHandler can not be initialized. Help making it better by reporting this as a bug.",
throwable
)
}
}
/**
* INTERNAL method that tells if the app has crashed in the last seconds.
* This is used to avoid restart loops.
*
* @return true if the app has crashed in the last seconds, false otherwise.
*/
private fun hasCrashedInTheLastSeconds(context: Context): Boolean {
val lastTimestamp = getLastCrashTimestamp(context)
val currentTimestamp: Long = Date().getTime()
return lastTimestamp <= currentTimestamp && currentTimestamp - lastTimestamp < 3000
}
@SuppressLint("ApplySharedPref")
private fun setLastCrashTimestamp(context: Context, timestamp: Long) {
context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE).edit()
.putLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, timestamp).commit()
}
private fun killCurrentProcess() {
// Process.killProcess(Process.myPid())
exitProcess(10)
}
private fun getLastCrashTimestamp(context: Context): Long {
return context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE)
.getLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, -1)
}
fun closeApplication(activity: Activity) {
activity.finish()
killCurrentProcess()
}
inner class Builder(context: Context) {
val context: Context
var isUCEHEnabled = true
var commaSeparatedEmailAddresses: String? = null
var isTrackActivitiesEnabled = false
var isBackgroundModeEnabled = true
fun setUCEHEnabled(isUCEHEnabled: Boolean): Builder {
this.isUCEHEnabled = isUCEHEnabled
return this
}
fun setTrackActivitiesEnabled(isTrackActivitiesEnabled: Boolean): Builder {
this.isTrackActivitiesEnabled = isTrackActivitiesEnabled
return this
}
fun setBackgroundModeEnabled(isBackgroundModeEnabled: Boolean): Builder {
this.isBackgroundModeEnabled = isBackgroundModeEnabled
return this
}
fun addCommaSeparatedEmailAddresses(commaSeparatedEmailAddresses: String?): Builder {
this.commaSeparatedEmailAddresses = commaSeparatedEmailAddresses ?: ""
return this
}
fun build() {
return UCEHandler(this)
}
init {
this.context = context
}
}
}

@ -0,0 +1,467 @@
package com.hmg.hmgDr.util.audio
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioRecord
import android.media.AudioTrack
import android.media.MediaRecorder.AudioSource
import android.os.Process
import android.util.Log
import com.opentok.android.BaseAudioDevice
import java.nio.ByteBuffer
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.ReentrantLock
class CustomAudioDevice(context: Context) : BaseAudioDevice() {
private val m_context: Context = context
private var m_audioTrack: AudioTrack? = null
private var m_audioRecord: AudioRecord? = null
// Capture & render buffers
private var m_playBuffer: ByteBuffer? = null
private var m_recBuffer: ByteBuffer? = null
private val m_tempBufPlay: ByteArray
private val m_tempBufRec: ByteArray
private val m_rendererLock: ReentrantLock = ReentrantLock(true)
private val m_renderEvent: Condition = m_rendererLock.newCondition()
@Volatile
private var m_isRendering = false
@Volatile
private var m_shutdownRenderThread = false
private val m_captureLock: ReentrantLock = ReentrantLock(true)
private val m_captureEvent: Condition = m_captureLock.newCondition()
@Volatile
private var m_isCapturing = false
@Volatile
private var m_shutdownCaptureThread = false
private val m_captureSettings: AudioSettings
private val m_rendererSettings: AudioSettings
// Capturing delay estimation
private var m_estimatedCaptureDelay = 0
// Rendering delay estimation
private var m_bufferedPlaySamples = 0
private var m_playPosition = 0
private var m_estimatedRenderDelay = 0
private val m_audioManager: AudioManager
private var isRendererMuted = false
companion object {
private const val LOG_TAG = "opentok-defaultaudio"
private const val SAMPLING_RATE = 44100
private const val NUM_CHANNELS_CAPTURING = 1
private const val NUM_CHANNELS_RENDERING = 1
private const val MAX_SAMPLES = 2 * 480 * 2 // Max 10 ms @ 48 kHz
}
init {
try {
m_playBuffer = ByteBuffer.allocateDirect(MAX_SAMPLES)
m_recBuffer = ByteBuffer.allocateDirect(MAX_SAMPLES)
} catch (e: Exception) {
Log.e(LOG_TAG, "${e.message}.")
}
m_tempBufPlay = ByteArray(MAX_SAMPLES)
m_tempBufRec = ByteArray(MAX_SAMPLES)
m_captureSettings = AudioSettings(
SAMPLING_RATE,
NUM_CHANNELS_CAPTURING
)
m_rendererSettings = AudioSettings(
SAMPLING_RATE,
NUM_CHANNELS_RENDERING
)
m_audioManager = m_context
.getSystemService(Context.AUDIO_SERVICE) as AudioManager
m_audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
}
override fun initCapturer(): Boolean {
// get the minimum buffer size that can be used
val minRecBufSize: Int = AudioRecord.getMinBufferSize(
m_captureSettings
.sampleRate,
if (NUM_CHANNELS_CAPTURING == 1) AudioFormat.CHANNEL_IN_MONO else AudioFormat.CHANNEL_IN_STEREO,
AudioFormat.ENCODING_PCM_16BIT
)
// double size to be more safe
val recBufSize = minRecBufSize * 2
// release the object
if (m_audioRecord != null) {
m_audioRecord!!.release()
m_audioRecord = null
}
try {
m_audioRecord = AudioRecord(
AudioSource.VOICE_COMMUNICATION,
m_captureSettings.sampleRate,
if (NUM_CHANNELS_CAPTURING == 1) AudioFormat.CHANNEL_IN_MONO else AudioFormat.CHANNEL_IN_STEREO,
AudioFormat.ENCODING_PCM_16BIT, recBufSize
)
} catch (e: Exception) {
Log.e(LOG_TAG, "${e.message}.")
return false
}
// check that the audioRecord is ready to be used
if (m_audioRecord!!.state != AudioRecord.STATE_INITIALIZED) {
Log.i(
LOG_TAG, "Audio capture is not initialized "
+ m_captureSettings.sampleRate
)
return false
}
m_shutdownCaptureThread = false
Thread(m_captureThread).start()
return true
}
override fun destroyCapturer(): Boolean {
m_captureLock.lock()
// release the object
m_audioRecord?.release()
m_audioRecord = null
m_shutdownCaptureThread = true
m_captureEvent.signal()
m_captureLock.unlock()
return true
}
override fun getEstimatedCaptureDelay(): Int {
return m_estimatedCaptureDelay
}
override fun startCapturer(): Boolean {
// start recording
try {
m_audioRecord!!.startRecording()
} catch (e: IllegalStateException) {
e.printStackTrace()
return false
}
m_captureLock.lock()
m_isCapturing = true
m_captureEvent.signal()
m_captureLock.unlock()
return true
}
override fun stopCapturer(): Boolean {
m_captureLock.lock()
try {
// only stop if we are recording
if (m_audioRecord!!.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
// stop recording
try {
m_audioRecord!!.stop()
} catch (e: IllegalStateException) {
e.printStackTrace()
return false
}
}
} finally {
// Ensure we always unlock
m_isCapturing = false
m_captureLock.unlock()
}
return true
}
private val m_captureThread = Runnable {
val samplesToRec = SAMPLING_RATE / 100
var samplesRead = 0
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
} catch (e: Exception) {
e.printStackTrace()
}
while (!m_shutdownCaptureThread) {
m_captureLock.lock()
samplesRead = try {
if (!m_isCapturing) {
m_captureEvent.await()
continue
} else {
if (m_audioRecord == null) {
continue
}
val lengthInBytes = ((samplesToRec shl 1)
* NUM_CHANNELS_CAPTURING)
val readBytes: Int = m_audioRecord!!.read(
m_tempBufRec, 0,
lengthInBytes
)
m_recBuffer!!.rewind()
m_recBuffer!!.put(m_tempBufRec)
(readBytes shr 1) / NUM_CHANNELS_CAPTURING
}
} catch (e: Exception) {
Log.e(LOG_TAG, "RecordAudio try failed: " + e.message)
continue
} finally {
// Ensure we always unlock
m_captureLock.unlock()
}
audioBus.writeCaptureData(m_recBuffer, samplesRead)
m_estimatedCaptureDelay = samplesRead * 1000 / SAMPLING_RATE
}
}
override fun initRenderer(): Boolean {
// get the minimum buffer size that can be used
val minPlayBufSize: Int = AudioTrack.getMinBufferSize(
m_rendererSettings
.sampleRate,
if (NUM_CHANNELS_RENDERING == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT
)
var playBufSize = minPlayBufSize
if (playBufSize < 6000) {
playBufSize *= 2
}
// release the object
if (m_audioTrack != null) {
m_audioTrack!!.release()
m_audioTrack = null
}
try {
m_audioTrack = AudioTrack(
AudioManager.STREAM_VOICE_CALL,
m_rendererSettings.sampleRate,
if (NUM_CHANNELS_RENDERING == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT, playBufSize,
AudioTrack.MODE_STREAM
)
} catch (e: Exception) {
Log.e(LOG_TAG, "${e.message}.")
return false
}
// check that the audioRecord is ready to be used
if (m_audioTrack!!.state != AudioTrack.STATE_INITIALIZED) {
Log.i(
LOG_TAG, "Audio renderer not initialized "
+ m_rendererSettings.sampleRate
)
return false
}
m_bufferedPlaySamples = 0
setOutputMode(OutputMode.SpeakerPhone)
m_shutdownRenderThread = false
Thread(m_renderThread).start()
return true
}
override fun destroyRenderer(): Boolean {
m_rendererLock.lock()
// release the object
m_audioTrack!!.release()
m_audioTrack = null
m_shutdownRenderThread = true
m_renderEvent.signal()
m_rendererLock.unlock()
unregisterHeadsetReceiver()
m_audioManager.isSpeakerphoneOn = false
m_audioManager.mode = AudioManager.MODE_NORMAL
return true
}
override fun getEstimatedRenderDelay(): Int {
return m_estimatedRenderDelay
}
override fun startRenderer(): Boolean {
// start playout
try {
m_audioTrack!!.play()
} catch (e: IllegalStateException) {
e.printStackTrace()
return false
}
m_rendererLock.lock()
m_isRendering = true
m_renderEvent.signal()
m_rendererLock.unlock()
return true
}
override fun stopRenderer(): Boolean {
m_rendererLock.lock()
try {
// only stop if we are playing
if (m_audioTrack!!.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
// stop playout
try {
m_audioTrack!!.stop()
} catch (e: IllegalStateException) {
e.printStackTrace()
return false
}
// flush the buffers
m_audioTrack!!.flush()
}
} finally {
// Ensure we always unlock, both for success, exception or error
// return.
m_isRendering = false
m_rendererLock.unlock()
}
return true
}
private val m_renderThread = Runnable {
val samplesToPlay = SAMPLING_RATE / 100
try {
Process
.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
} catch (e: Exception) {
e.printStackTrace()
}
while (!m_shutdownRenderThread) {
m_rendererLock.lock()
try {
if (!m_isRendering) {
m_renderEvent.await()
continue
} else {
m_rendererLock.unlock()
// Don't lock on audioBus calls
m_playBuffer!!.clear()
val samplesRead: Int = audioBus.readRenderData(
m_playBuffer, samplesToPlay
)
// Log.d(LOG_TAG, "Samples read: " + samplesRead);
m_rendererLock.lock()
if (!isRendererMuted) {
// After acquiring the lock again
// we must check if we are still playing
if (m_audioTrack == null
|| !m_isRendering
) {
continue
}
val bytesRead = ((samplesRead shl 1)
* NUM_CHANNELS_RENDERING)
m_playBuffer!!.get(m_tempBufPlay, 0, bytesRead)
val bytesWritten: Int = m_audioTrack!!.write(
m_tempBufPlay, 0,
bytesRead
)
// increase by number of written samples
m_bufferedPlaySamples += ((bytesWritten shr 1)
/ NUM_CHANNELS_RENDERING)
// decrease by number of played samples
val pos: Int = m_audioTrack!!.getPlaybackHeadPosition()
if (pos < m_playPosition) {
// wrap or reset by driver
m_playPosition = 0
}
m_bufferedPlaySamples -= pos - m_playPosition
m_playPosition = pos
// we calculate the estimated delay based on the
// buffered samples
m_estimatedRenderDelay = (m_bufferedPlaySamples * 1000
/ SAMPLING_RATE)
}
}
} catch (e: Exception) {
Log.e(LOG_TAG, "Exception: " + e.message)
e.printStackTrace()
} finally {
m_rendererLock.unlock()
}
}
}
override fun getCaptureSettings(): AudioSettings {
return m_captureSettings
}
override fun getRenderSettings(): AudioSettings {
return m_rendererSettings
}
/**
* Communication modes handling
*/
override fun setOutputMode(mode: OutputMode): Boolean {
super.setOutputMode(mode)
if (mode == OutputMode.Handset) {
unregisterHeadsetReceiver()
m_audioManager.isSpeakerphoneOn = false
} else {
m_audioManager.isSpeakerphoneOn = true
registerHeadsetReceiver()
}
return true
}
private val m_headsetReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action!!.compareTo(Intent.ACTION_HEADSET_PLUG) == 0) {
val state: Int = intent.getIntExtra("state", 0)
m_audioManager.isSpeakerphoneOn = state == 0
}
}
}
private var m_receiverRegistered = false
private fun registerHeadsetReceiver() {
if (!m_receiverRegistered) {
val receiverFilter = IntentFilter(
Intent.ACTION_HEADSET_PLUG
)
m_context.registerReceiver(m_headsetReceiver, receiverFilter)
m_receiverRegistered = true
}
}
private fun unregisterHeadsetReceiver() {
if (m_receiverRegistered) {
try {
m_context.unregisterReceiver(m_headsetReceiver)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
}
m_receiverRegistered = false
}
}
override fun onPause() {
if (outputMode == OutputMode.SpeakerPhone) {
unregisterHeadsetReceiver()
}
}
override fun onResume() {
if (outputMode == OutputMode.SpeakerPhone) {
registerHeadsetReceiver()
}
}
fun setRendererMute(isRendererMuted: Boolean) {
this.isRendererMuted = isRendererMuted
}
}

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fadeScrollbars="false"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/ask_for_error_log"
android:textColor="#212121"
android:textSize="18sp"/>
<Button
android:id="@+id/button_view_error_log"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="View Error Log"
android:textColor="#212121"/>
<Button
android:id="@+id/button_copy_error_log"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Copy Error Log"
android:textColor="#212121"/>
<Button
android:id="@+id/button_share_error_log"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Share Error Log"
android:textColor="#212121"/>
<Button
android:id="@+id/button_email_error_log"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Email Error Log"
android:textColor="#212121"/>
<Button
android:id="@+id/button_save_error_log"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Save Error Log"
android:textColor="#212121"/>
<Button
android:id="@+id/button_close_app"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Close App"
android:textColor="#212121"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

@ -4,7 +4,6 @@
<string name="remaining_ar">الوقت المتبقي بالثانيه: </string>
<string name="setting">Settings</string>
<string name="cancel">Cancel</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="ask_for_error_log">An unexpected error has occurred.\nHelp developers by providing error details.\nThank you for your support.</string>
</resources>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
</paths>

@ -51,12 +51,14 @@ class VideoCallService extends BaseService {
onCallConnected: onCallConnected,
onCallDisconnected: onCallDisconnected,
onCallEnd: () {
WidgetsBinding.instance.addPostFrameCallback((_) async {
// mosa todo uncomment
/* WidgetsBinding.instance.addPostFrameCallback((_) async {
GifLoaderDialogUtils.showMyDialog(
locator<NavigationService>().navigatorKey.currentContext);
endCall(
patient.vcId,
false,
).then((value) {
GifLoaderDialogUtils.hideDialog(
locator<NavigationService>().navigatorKey.currentContext);
@ -68,7 +70,7 @@ class VideoCallService extends BaseService {
"patient": patient,
});
});
});
});*/
},
onCallNotRespond: (SessionStatusModel sessionStatusModel) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -94,16 +96,17 @@ class VideoCallService extends BaseService {
}
Future endCall(int vCID, bool isPatient) async {
hasError = false;
await getDoctorProfile(isGetProfile: true);
EndCallReq endCallReq = new EndCallReq();
endCallReq.doctorId = doctorProfile.doctorID;
endCallReq.generalid = 'Cs2020@2016\$2958';
endCallReq.vCID = vCID;
endCallReq.isDestroy = isPatient;
await _liveCarePatientServices.endCall(endCallReq);
if (_liveCarePatientServices.hasError) {
error = _liveCarePatientServices.error;
}
// mosa todo uncomment
// hasError = false;
// await getDoctorProfile(isGetProfile: true);
// EndCallReq endCallReq = new EndCallReq();
// endCallReq.doctorId = doctorProfile.doctorID;
// endCallReq.generalid = 'Cs2020@2016\$2958';
// endCallReq.vCID = vCID;
// endCallReq.isDestroy = isPatient;
// await _liveCarePatientServices.endCall(endCallReq);
// if (_liveCarePatientServices.hasError) {
// error = _liveCarePatientServices.error;
// }
}
}

@ -255,7 +255,7 @@ class AuthenticationViewModel extends BaseViewModel {
/// add  token to shared preferences in case of send activation code is success
setDataAfterSendActivationSuccess(SendActivationCodeForDoctorAppResponseModel sendActivationCodeForDoctorAppResponseModel) {
print("VerificationCode : " + sendActivationCodeForDoctorAppResponseModel.verificationCode);
// DrAppToastMsg.showSuccesToast("_VerificationCode_ : " + sendActivationCodeForDoctorAppResponseModel.verificationCode);
// DrAppToastMsg.showSuccesToast("mosa_VerificationCode_ : " + sendActivationCodeForDoctorAppResponseModel.verificationCode);
sharedPref.setString(VIDA_AUTH_TOKEN_ID,
sendActivationCodeForDoctorAppResponseModel.vidaAuthTokenID);
sharedPref.setString(VIDA_REFRESH_TOKEN_ID,

Loading…
Cancel
Save