Compare commits
No commits in common. 'development' and 'flutter_vervion_2' have entirely different histories.
developmen
...
flutter_ve
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.hmg.hmgDr">
|
||||||
|
<!-- Flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
@ -1,12 +0,0 @@
|
|||||||
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,4 +1,4 @@
|
|||||||
package com.hmg.hmgDr.model;
|
package com.hmg.hmgDr.Model;
|
||||||
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.hmg.hmgDr.model;
|
package com.hmg.hmgDr.Model;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
@ -1,234 +0,0 @@
|
|||||||
package com.hmg.hmgDr.Service
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.PixelFormat
|
|
||||||
import android.graphics.Point
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.CountDownTimer
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.*
|
|
||||||
import androidx.core.view.GestureDetectorCompat
|
|
||||||
import com.hmg.hmgDr.R
|
|
||||||
import com.hmg.hmgDr.util.ViewsUtil
|
|
||||||
|
|
||||||
abstract class BaseMovingFloatingWidget : Service() {
|
|
||||||
|
|
||||||
val szWindow = Point()
|
|
||||||
lateinit var windowManagerParams: WindowManager.LayoutParams
|
|
||||||
var mWindowManager: WindowManager? = null
|
|
||||||
var floatingWidgetView: View? = null
|
|
||||||
lateinit var floatingViewContainer: View
|
|
||||||
|
|
||||||
lateinit var mDetector: GestureDetectorCompat
|
|
||||||
|
|
||||||
private var xInitCord = 0
|
|
||||||
private var yInitCord: Int = 0
|
|
||||||
private var xInitMargin: Int = 0
|
|
||||||
private var yInitMargin: Int = 0
|
|
||||||
|
|
||||||
/* Add Floating Widget View to Window Manager */
|
|
||||||
open fun addFloatingWidgetView() {
|
|
||||||
mWindowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
|
||||||
|
|
||||||
//Init LayoutInflater
|
|
||||||
val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
|
||||||
//Inflate the removing view layout we created
|
|
||||||
floatingWidgetView = inflater.inflate(R.layout.activity_video_call, null)
|
|
||||||
|
|
||||||
//Add the view to the window.
|
|
||||||
windowManagerParams =
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
||||||
WindowManager.LayoutParams(
|
|
||||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
|
||||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
|
||||||
WindowManager.LayoutParams.TYPE_PHONE,
|
|
||||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
|
||||||
PixelFormat.TRANSLUCENT
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
WindowManager.LayoutParams(
|
|
||||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
|
||||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
|
||||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
|
|
||||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
|
||||||
PixelFormat.TRANSLUCENT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Specify the view position
|
|
||||||
windowManagerParams.gravity = Gravity.TOP or Gravity.START
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
val dragListener: View.OnTouchListener = View.OnTouchListener { _, event ->
|
|
||||||
mDetector.onTouchEvent(event)
|
|
||||||
|
|
||||||
//Get Floating widget view params
|
|
||||||
val layoutParams: WindowManager.LayoutParams =
|
|
||||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
|
||||||
//get the touch location coordinates
|
|
||||||
val x_cord = event.rawX.toInt()
|
|
||||||
val y_cord = event.rawY.toInt()
|
|
||||||
val x_cord_Destination: Int
|
|
||||||
var y_cord_Destination: Int
|
|
||||||
|
|
||||||
when (event.action) {
|
|
||||||
MotionEvent.ACTION_DOWN -> {
|
|
||||||
xInitCord = x_cord
|
|
||||||
yInitCord = y_cord
|
|
||||||
|
|
||||||
//remember the initial position.
|
|
||||||
xInitMargin = layoutParams.x
|
|
||||||
yInitMargin = layoutParams.y
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_UP -> {
|
|
||||||
//Get the difference between initial coordinate and current coordinate
|
|
||||||
val x_diff: Int = x_cord - xInitCord
|
|
||||||
val y_diff: Int = y_cord - yInitCord
|
|
||||||
|
|
||||||
y_cord_Destination = yInitMargin + y_diff
|
|
||||||
val barHeight: Int = ViewsUtil.getStatusBarHeight(applicationContext)
|
|
||||||
if (y_cord_Destination < 0) {
|
|
||||||
y_cord_Destination = 0
|
|
||||||
// y_cord_Destination =
|
|
||||||
// -(szWindow.y - (videoCallContainer.height /*+ barHeight*/))
|
|
||||||
// y_cord_Destination = -(szWindow.y / 2)
|
|
||||||
} else if (y_cord_Destination + (floatingViewContainer.height + barHeight) > szWindow.y) {
|
|
||||||
y_cord_Destination = szWindow.y - (floatingViewContainer.height + barHeight)
|
|
||||||
// y_cord_Destination = (szWindow.y / 2)
|
|
||||||
}
|
|
||||||
layoutParams.y = y_cord_Destination
|
|
||||||
|
|
||||||
//reset position if user drags the floating view
|
|
||||||
resetPosition(x_cord)
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
|
||||||
val x_diff_move: Int = x_cord - xInitCord
|
|
||||||
val y_diff_move: Int = y_cord - yInitCord
|
|
||||||
x_cord_Destination = xInitMargin + x_diff_move
|
|
||||||
y_cord_Destination = yInitMargin + y_diff_move
|
|
||||||
|
|
||||||
layoutParams.x = x_cord_Destination
|
|
||||||
layoutParams.y = y_cord_Destination
|
|
||||||
|
|
||||||
//Update the layout with new X & Y coordinate
|
|
||||||
mWindowManager?.updateViewLayout(floatingWidgetView, layoutParams)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OnTouch actions
|
|
||||||
*/
|
|
||||||
class MyGestureListener(
|
|
||||||
val onTabCall: () -> Unit,
|
|
||||||
val miniCircleDoubleTap: () -> Unit
|
|
||||||
) : GestureDetector.SimpleOnGestureListener() {
|
|
||||||
|
|
||||||
override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
|
|
||||||
// onTabCall()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDoubleTap(e: MotionEvent?): Boolean {
|
|
||||||
miniCircleDoubleTap()
|
|
||||||
return super.onDoubleTap(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset position of Floating Widget view on dragging */
|
|
||||||
fun resetPosition(x_cord_now: Int) {
|
|
||||||
if (x_cord_now <= szWindow.x / 2) {
|
|
||||||
moveToLeft(x_cord_now)
|
|
||||||
} else {
|
|
||||||
moveToRight(x_cord_now)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Method to move the Floating widget view to Left */
|
|
||||||
private fun moveToLeft(current_x_cord: Int) {
|
|
||||||
|
|
||||||
val mParams: WindowManager.LayoutParams =
|
|
||||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
|
||||||
|
|
||||||
mParams.x =
|
|
||||||
(szWindow.x - current_x_cord * current_x_cord - floatingViewContainer.width).toInt()
|
|
||||||
|
|
||||||
try {
|
|
||||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
|
||||||
}
|
|
||||||
val x = szWindow.x - current_x_cord
|
|
||||||
object : CountDownTimer(500, 5) {
|
|
||||||
//get params of Floating Widget view
|
|
||||||
val mParams: WindowManager.LayoutParams =
|
|
||||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
|
||||||
|
|
||||||
override fun onTick(t: Long) {
|
|
||||||
val step = (500 - t) / 5
|
|
||||||
// mParams.x = 0 - (current_x_cord * current_x_cord * step).toInt()
|
|
||||||
mParams.x =
|
|
||||||
(szWindow.x - current_x_cord * current_x_cord * step - floatingViewContainer.width).toInt()
|
|
||||||
|
|
||||||
try {
|
|
||||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFinish() {
|
|
||||||
mParams.x = -(szWindow.x - floatingViewContainer.width)
|
|
||||||
|
|
||||||
try {
|
|
||||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Method to move the Floating widget view to Right */
|
|
||||||
private fun moveToRight(current_x_cord: Int) {
|
|
||||||
object : CountDownTimer(500, 5) {
|
|
||||||
//get params of Floating Widget view
|
|
||||||
val mParams: WindowManager.LayoutParams =
|
|
||||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
|
||||||
|
|
||||||
override fun onTick(t: Long) {
|
|
||||||
val step = (500 - t) / 5
|
|
||||||
mParams.x =
|
|
||||||
(szWindow.x + current_x_cord * current_x_cord * step - floatingViewContainer.width).toInt()
|
|
||||||
|
|
||||||
try {
|
|
||||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFinish() {
|
|
||||||
mParams.x = szWindow.x - floatingViewContainer.width
|
|
||||||
|
|
||||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
|
||||||
}
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Utils
|
|
||||||
*/
|
|
||||||
|
|
||||||
fun getWindowManagerDefaultDisplay() {
|
|
||||||
val w = mWindowManager!!.defaultDisplay.width
|
|
||||||
val h = mWindowManager!!.defaultDisplay.height
|
|
||||||
szWindow[w] = h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
package com.hmg.hmgDr.Service
|
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Binder
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.IBinder
|
|
||||||
import com.hmg.hmgDr.MainActivity
|
|
||||||
import com.hmg.hmgDr.ui.VideoCallResponseListener
|
|
||||||
import com.hmg.hmgDr.ui.fragment.VideoCallFragment
|
|
||||||
|
|
||||||
class VideoStreamContainerService : Service(), VideoCallResponseListener {
|
|
||||||
|
|
||||||
var videoCallResponseListener: VideoCallResponseListener? = null
|
|
||||||
var mActivity: MainActivity? = null
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
if (field != null) {
|
|
||||||
setDialogFragment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var arguments: Bundle? = null
|
|
||||||
var serviceRunning: Boolean = false
|
|
||||||
|
|
||||||
|
|
||||||
private val serviceBinder: IBinder = VideoStreamBinder()
|
|
||||||
private var dialogFragment: VideoCallFragment? = null
|
|
||||||
|
|
||||||
override fun onBind(intent: Intent?): IBinder {
|
|
||||||
return serviceBinder
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setDialogFragment() {
|
|
||||||
mActivity?.run {
|
|
||||||
if (dialogFragment == null) {
|
|
||||||
val transaction = supportFragmentManager.beginTransaction()
|
|
||||||
dialogFragment = VideoCallFragment.newInstance(arguments!!)
|
|
||||||
dialogFragment?.let {
|
|
||||||
it.setCallListener(this@VideoStreamContainerService)
|
|
||||||
it.isCancelable = true
|
|
||||||
if (it.isAdded) {
|
|
||||||
it.dismiss()
|
|
||||||
} else {
|
|
||||||
it.show(transaction, "dialog")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (!dialogFragment!!.isVisible) {
|
|
||||||
val transaction = supportFragmentManager.beginTransaction()
|
|
||||||
dialogFragment!!.show(transaction, "dialog")
|
|
||||||
} else {
|
|
||||||
// don't do anything
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun closeVideoCall(){
|
|
||||||
dialogFragment?.onCallClicked()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
||||||
if (intent != null && intent.extras != null) {
|
|
||||||
arguments = intent.extras
|
|
||||||
}
|
|
||||||
// Toast.makeText(this, "Service started by user.", Toast.LENGTH_LONG).show()
|
|
||||||
return START_STICKY
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
// Toast.makeText(this, "Service destroyed by user.", Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class VideoStreamBinder : Binder() {
|
|
||||||
val service: VideoStreamContainerService
|
|
||||||
get() = this@VideoStreamContainerService
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCallFinished(resultCode: Int, intent: Intent?) {
|
|
||||||
dialogFragment = null
|
|
||||||
videoCallResponseListener?.onCallFinished(resultCode, intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun errorHandle(message: String) {
|
|
||||||
dialogFragment = null
|
|
||||||
// Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun minimizeVideoEvent(isMinimize: Boolean) {
|
|
||||||
videoCallResponseListener?.minimizeVideoEvent(isMinimize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
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()
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
package com.hmg.hmgDr.globalErrorHandler
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
|
|
||||||
class UCEDefaultActivity : AppCompatActivity() {
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
package com.hmg.hmgDr.globalErrorHandler
|
|
||||||
|
|
||||||
import androidx.core.content.FileProvider
|
|
||||||
|
|
||||||
class UCEFileProvider : FileProvider() {
|
|
||||||
}
|
|
||||||
@ -1,280 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
package com.hmg.hmgDr.model
|
|
||||||
|
|
||||||
/** Represents standard data needed for a Notification. */
|
|
||||||
open class NotificationDataModel(
|
|
||||||
// Standard notification values:
|
|
||||||
var mContentTitle: String,
|
|
||||||
var mContentText: String,
|
|
||||||
var mPriority: Int ,
|
|
||||||
// Notification channel values (O and above):
|
|
||||||
var mChannelId: String,
|
|
||||||
var mChannelName: CharSequence,
|
|
||||||
var mChannelDescription: String,
|
|
||||||
var mChannelImportance: Int ,
|
|
||||||
var mChannelEnableVibrate: Boolean ,
|
|
||||||
var mChannelLockscreenVisibility: Int
|
|
||||||
)
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
package com.hmg.hmgDr.model
|
|
||||||
|
|
||||||
import android.app.Notification
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
|
|
||||||
class NotificationVideoModel constructor(
|
|
||||||
mContentTitle: String,
|
|
||||||
mContentText: String,
|
|
||||||
mChannelId: String,
|
|
||||||
mChannelName: CharSequence,
|
|
||||||
mChannelDescription: String,
|
|
||||||
mPriority: Int = Notification.PRIORITY_MAX,
|
|
||||||
mChannelImportance: Int = NotificationManager.IMPORTANCE_LOW,
|
|
||||||
mChannelEnableVibrate: Boolean = true,
|
|
||||||
mChannelLockscreenVisibility: Int = NotificationCompat.VISIBILITY_PUBLIC,
|
|
||||||
// Unique data for this Notification.Style:
|
|
||||||
var mBigContentTitle: String = mContentTitle,
|
|
||||||
val mBigText: String = mContentText,
|
|
||||||
var mSummaryText: String
|
|
||||||
) : NotificationDataModel(
|
|
||||||
mContentTitle,
|
|
||||||
mContentText,
|
|
||||||
mPriority,
|
|
||||||
mChannelId,
|
|
||||||
mChannelName,
|
|
||||||
mChannelDescription,
|
|
||||||
mChannelImportance,
|
|
||||||
mChannelEnableVibrate,
|
|
||||||
mChannelLockscreenVisibility
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -0,0 +1,442 @@
|
|||||||
|
package com.hmg.hmgDr.ui;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.opengl.GLSurfaceView;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.CountDownTimer;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel;
|
||||||
|
import com.hmg.hmgDr.Model.GetSessionStatusModel;
|
||||||
|
import com.hmg.hmgDr.Model.SessionStatusModel;
|
||||||
|
import com.hmg.hmgDr.R;
|
||||||
|
import com.opentok.android.Session;
|
||||||
|
import com.opentok.android.Stream;
|
||||||
|
import com.opentok.android.Publisher;
|
||||||
|
import com.opentok.android.PublisherKit;
|
||||||
|
import com.opentok.android.Subscriber;
|
||||||
|
import com.opentok.android.BaseVideoRenderer;
|
||||||
|
import com.opentok.android.OpentokError;
|
||||||
|
import com.opentok.android.SubscriberKit;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import pub.devrel.easypermissions.AfterPermissionGranted;
|
||||||
|
import pub.devrel.easypermissions.AppSettingsDialog;
|
||||||
|
import pub.devrel.easypermissions.EasyPermissions;
|
||||||
|
|
||||||
|
public class VideoCallActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks,
|
||||||
|
Session.SessionListener,
|
||||||
|
Publisher.PublisherListener,
|
||||||
|
Subscriber.VideoListener, VideoCallContract.VideoCallView {
|
||||||
|
|
||||||
|
private static final String TAG = VideoCallActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
VideoCallContract.VideoCallPresenter videoCallPresenter;
|
||||||
|
|
||||||
|
private static final int RC_SETTINGS_SCREEN_PERM = 123;
|
||||||
|
private static final int RC_VIDEO_APP_PERM = 124;
|
||||||
|
|
||||||
|
|
||||||
|
private Session mSession;
|
||||||
|
private Publisher mPublisher;
|
||||||
|
private Subscriber mSubscriber;
|
||||||
|
|
||||||
|
private Handler mVolHandler, mConnectedHandler;
|
||||||
|
private Runnable mVolRunnable, mConnectedRunnable;
|
||||||
|
|
||||||
|
private FrameLayout mPublisherViewContainer;
|
||||||
|
private RelativeLayout mSubscriberViewContainer;
|
||||||
|
private RelativeLayout controlPanel;
|
||||||
|
|
||||||
|
private String apiKey;
|
||||||
|
private String sessionId;
|
||||||
|
private String token;
|
||||||
|
private String appLang;
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
private boolean isSwitchCameraClicked;
|
||||||
|
private boolean isCameraClicked;
|
||||||
|
private boolean isSpeckerClicked;
|
||||||
|
private boolean isMicClicked;
|
||||||
|
|
||||||
|
private ImageView mCallBtn;
|
||||||
|
private ImageView mCameraBtn;
|
||||||
|
private ImageView mSwitchCameraBtn;
|
||||||
|
private ImageView mspeckerBtn;
|
||||||
|
private ImageView mMicBtn;
|
||||||
|
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
private CountDownTimer countDownTimer;
|
||||||
|
private TextView progressBarTextView;
|
||||||
|
private RelativeLayout progressBarLayout;
|
||||||
|
|
||||||
|
private boolean isConnected = false;
|
||||||
|
|
||||||
|
private GetSessionStatusModel sessionStatusModel;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
setTheme(R.style.AppTheme);
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_video_call);
|
||||||
|
Objects.requireNonNull(getSupportActionBar()).hide();
|
||||||
|
initUI();
|
||||||
|
requestPermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
|
||||||
|
if (mSession == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mSession.onPause();
|
||||||
|
|
||||||
|
if (isFinishing()) {
|
||||||
|
disconnectSession();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
if (mSession == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mSession.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
disconnectSession();
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
private void initUI() {
|
||||||
|
mPublisherViewContainer = (FrameLayout) findViewById(R.id.local_video_view_container);
|
||||||
|
mSubscriberViewContainer = (RelativeLayout) findViewById(R.id.remote_video_view_container);
|
||||||
|
|
||||||
|
apiKey = getIntent().getStringExtra("apiKey");
|
||||||
|
sessionId = getIntent().getStringExtra("sessionId");
|
||||||
|
token = getIntent().getStringExtra("token");
|
||||||
|
appLang = getIntent().getStringExtra("appLang");
|
||||||
|
baseUrl = getIntent().getStringExtra("baseUrl");
|
||||||
|
sessionStatusModel = getIntent().getParcelableExtra("sessionStatusModel");
|
||||||
|
|
||||||
|
controlPanel = findViewById(R.id.control_panel);
|
||||||
|
|
||||||
|
videoCallPresenter = new VideoCallPresenterImpl(this, baseUrl);
|
||||||
|
|
||||||
|
|
||||||
|
mCallBtn = findViewById(R.id.btn_call);
|
||||||
|
mCameraBtn = findViewById(R.id.btn_camera);
|
||||||
|
mSwitchCameraBtn = findViewById(R.id.btn_switch_camera);
|
||||||
|
mspeckerBtn = findViewById(R.id.btn_specker);
|
||||||
|
mMicBtn = findViewById(R.id.btn_mic);
|
||||||
|
|
||||||
|
// progressBarLayout=findViewById(R.id.progressBar);
|
||||||
|
// progressBar=findViewById(R.id.progress_bar);
|
||||||
|
// progressBarTextView=findViewById(R.id.progress_bar_text);
|
||||||
|
// progressBar.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
hiddenButtons();
|
||||||
|
|
||||||
|
checkClientConnected();
|
||||||
|
|
||||||
|
mSubscriberViewContainer.setOnTouchListener((v, event) -> {
|
||||||
|
controlPanel.setVisibility(View.VISIBLE);
|
||||||
|
mVolHandler.removeCallbacks(mVolRunnable);
|
||||||
|
mVolHandler.postDelayed(mVolRunnable, 5 * 1000);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (appLang.equals("ar")) {
|
||||||
|
progressBarLayout.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkClientConnected() {
|
||||||
|
mConnectedHandler = new Handler();
|
||||||
|
mConnectedRunnable = () -> {
|
||||||
|
if (!isConnected) {
|
||||||
|
videoCallPresenter.callClintConnected(sessionStatusModel);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mConnectedHandler.postDelayed(mConnectedRunnable, 30 * 1000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hiddenButtons() {
|
||||||
|
mVolHandler = new Handler();
|
||||||
|
mVolRunnable = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
controlPanel.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mVolHandler.postDelayed(mVolRunnable, 5 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
|
||||||
|
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPermissionsGranted(int requestCode, List<String> perms) {
|
||||||
|
Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPermissionsDenied(int requestCode, List<String> perms) {
|
||||||
|
Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());
|
||||||
|
|
||||||
|
if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
|
||||||
|
new 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterPermissionGranted(RC_VIDEO_APP_PERM)
|
||||||
|
private void requestPermissions() {
|
||||||
|
String[] perms = {Manifest.permission.INTERNET, Manifest.permission.CAMERA,};
|
||||||
|
if (EasyPermissions.hasPermissions(this, perms)) {
|
||||||
|
try {
|
||||||
|
mSession = new Session.Builder(this, apiKey, sessionId).build();
|
||||||
|
mSession.setSessionListener(this);
|
||||||
|
mSession.connect(token);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
EasyPermissions.requestPermissions(this, getString(R.string.remaining_ar), RC_VIDEO_APP_PERM, perms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnected(Session session) {
|
||||||
|
Log.i(TAG, "Session Connected");
|
||||||
|
|
||||||
|
mPublisher = new Publisher.Builder(this).build();
|
||||||
|
mPublisher.setPublisherListener(this);
|
||||||
|
|
||||||
|
mPublisherViewContainer.addView(mPublisher.getView());
|
||||||
|
|
||||||
|
if (mPublisher.getView() instanceof GLSurfaceView) {
|
||||||
|
((GLSurfaceView) mPublisher.getView()).setZOrderOnTop(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
mSession.publish(mPublisher);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisconnected(Session session) {
|
||||||
|
Log.d(TAG, "onDisconnected: disconnected from session " + session.getSessionId());
|
||||||
|
|
||||||
|
mSession = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Session session, OpentokError opentokError) {
|
||||||
|
Log.d(TAG, "onError: Error (" + opentokError.getMessage() + ") in session " + session.getSessionId());
|
||||||
|
|
||||||
|
Toast.makeText(this, "Session error. See the logcat please.", Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStreamReceived(Session session, Stream stream) {
|
||||||
|
Log.d(TAG, "onStreamReceived: New stream " + stream.getStreamId() + " in session " + session.getSessionId());
|
||||||
|
if (mSubscriber != null) {
|
||||||
|
isConnected = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isConnected = true;
|
||||||
|
subscribeToStream(stream);
|
||||||
|
videoCallPresenter.callChangeCallStatus(new ChangeCallStatusRequestModel(3,sessionStatusModel.getDoctorId(), sessionStatusModel.getGeneralid(),token,sessionStatusModel.getVCID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStreamDropped(Session session, Stream stream) {
|
||||||
|
Log.d(TAG, "onStreamDropped: Stream " + stream.getStreamId() + " dropped from session " + session.getSessionId());
|
||||||
|
|
||||||
|
if (mSubscriber == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mSubscriber.getStream().equals(stream)) {
|
||||||
|
mSubscriberViewContainer.removeView(mSubscriber.getView());
|
||||||
|
mSubscriber.destroy();
|
||||||
|
mSubscriber = null;
|
||||||
|
}
|
||||||
|
disconnectSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStreamCreated(PublisherKit publisherKit, Stream stream) {
|
||||||
|
Log.d(TAG, "onStreamCreated: Own stream " + stream.getStreamId() + " created");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStreamDestroyed(PublisherKit publisherKit, Stream stream) {
|
||||||
|
Log.d(TAG, "onStreamDestroyed: Own stream " + stream.getStreamId() + " destroyed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(PublisherKit publisherKit, OpentokError opentokError) {
|
||||||
|
Log.d(TAG, "onError: Error (" + opentokError.getMessage() + ") in publisher");
|
||||||
|
|
||||||
|
Toast.makeText(this, "Session error. See the logcat please.", Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoDataReceived(SubscriberKit subscriberKit) {
|
||||||
|
mSubscriber.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL);
|
||||||
|
mSubscriberViewContainer.addView(mSubscriber.getView());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoDisabled(SubscriberKit subscriberKit, String s) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoEnabled(SubscriberKit subscriberKit, String s) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoDisableWarning(SubscriberKit subscriberKit) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoDisableWarningLifted(SubscriberKit subscriberKit) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void subscribeToStream(Stream stream) {
|
||||||
|
mSubscriber = new Subscriber.Builder(VideoCallActivity.this, stream).build();
|
||||||
|
mSubscriber.setVideoListener(this);
|
||||||
|
mSession.subscribe(mSubscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disconnectSession() {
|
||||||
|
if (mSession == null) {
|
||||||
|
setResult(Activity.RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mSubscriber != null) {
|
||||||
|
mSubscriberViewContainer.removeView(mSubscriber.getView());
|
||||||
|
mSession.unsubscribe(mSubscriber);
|
||||||
|
mSubscriber.destroy();
|
||||||
|
mSubscriber = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPublisher != null) {
|
||||||
|
mPublisherViewContainer.removeView(mPublisher.getView());
|
||||||
|
mSession.unpublish(mPublisher);
|
||||||
|
mPublisher.destroy();
|
||||||
|
mPublisher = null;
|
||||||
|
}
|
||||||
|
mSession.disconnect();
|
||||||
|
if (countDownTimer != null) {
|
||||||
|
countDownTimer.cancel();
|
||||||
|
}
|
||||||
|
videoCallPresenter.callChangeCallStatus(new ChangeCallStatusRequestModel(16,sessionStatusModel.getDoctorId(), sessionStatusModel.getGeneralid(),token,sessionStatusModel.getVCID()));
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSwitchCameraClicked(View view) {
|
||||||
|
if (mPublisher != null) {
|
||||||
|
isSwitchCameraClicked = !isSwitchCameraClicked;
|
||||||
|
mPublisher.cycleCamera();
|
||||||
|
int res = isSwitchCameraClicked ? R.drawable.flip_disapled : R.drawable.flip_enabled;
|
||||||
|
mSwitchCameraBtn.setImageResource(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCameraClicked(View view) {
|
||||||
|
if (mPublisher != null) {
|
||||||
|
isCameraClicked = !isCameraClicked;
|
||||||
|
mPublisher.setPublishVideo(!isCameraClicked);
|
||||||
|
int res = isCameraClicked ? R.drawable.video_disanabled : R.drawable.video_enabled;
|
||||||
|
mCameraBtn.setImageResource(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSpeckerClicked(View view) {
|
||||||
|
if (mSubscriber != null) {
|
||||||
|
isSpeckerClicked = !isSpeckerClicked;
|
||||||
|
mSubscriber.setSubscribeToAudio(!isSpeckerClicked);
|
||||||
|
int res = isSpeckerClicked ? R.drawable.audio_disabled : R.drawable.audio_enabled;
|
||||||
|
mspeckerBtn.setImageResource(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMicClicked(View view) {
|
||||||
|
|
||||||
|
if (mPublisher != null) {
|
||||||
|
isMicClicked = !isMicClicked;
|
||||||
|
mPublisher.setPublishAudio(!isMicClicked);
|
||||||
|
int res = isMicClicked ? R.drawable.mic_disabled : R.drawable.mic_enabled;
|
||||||
|
mMicBtn.setImageResource(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCallClicked(View view) {
|
||||||
|
disconnectSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCallSuccessful(SessionStatusModel sessionStatusModel) {
|
||||||
|
if (sessionStatusModel.getSessionStatus() == 2 || sessionStatusModel.getSessionStatus() == 3) {
|
||||||
|
Intent returnIntent = new Intent();
|
||||||
|
returnIntent.putExtra("sessionStatusNotRespond", sessionStatusModel);
|
||||||
|
setResult(Activity.RESULT_OK, returnIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCallChangeCallStatusSuccessful(SessionStatusModel sessionStatusModel) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
package com.hmg.hmgDr.ui
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
|
|
||||||
interface VideoCallResponseListener {
|
|
||||||
|
|
||||||
fun onCallFinished(resultCode : Int, intent: Intent? = null)
|
|
||||||
|
|
||||||
fun errorHandle(message: String){}
|
|
||||||
|
|
||||||
fun minimizeVideoEvent(isMinimize : Boolean)
|
|
||||||
|
|
||||||
fun onBackHandle(){}
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
package com.hmg.hmgDr.util
|
|
||||||
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import com.hmg.hmgDr.model.NotificationDataModel
|
|
||||||
import com.hmg.hmgDr.model.NotificationVideoModel
|
|
||||||
|
|
||||||
object NotificationUtil {
|
|
||||||
|
|
||||||
fun createNotificationChannel(
|
|
||||||
context: Context,
|
|
||||||
notificationDataModel: NotificationDataModel
|
|
||||||
): String {
|
|
||||||
// The id of the channel.
|
|
||||||
val channelId: String = notificationDataModel.mChannelId
|
|
||||||
|
|
||||||
// NotificationChannels are required for Notifications on O (API 26) and above.
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
// The user-visible name of the channel.
|
|
||||||
val channelName: CharSequence = notificationDataModel.mChannelName
|
|
||||||
// The user-visible description of the channel.
|
|
||||||
val channelDescription: String = notificationDataModel.mChannelDescription
|
|
||||||
val channelImportance: Int = notificationDataModel.mChannelImportance
|
|
||||||
val channelEnableVibrate: Boolean = notificationDataModel.mChannelEnableVibrate
|
|
||||||
val channelLockscreenVisibility: Int =
|
|
||||||
notificationDataModel.mChannelLockscreenVisibility
|
|
||||||
|
|
||||||
// Initializes NotificationChannel.
|
|
||||||
val notificationChannel = NotificationChannel(channelId, channelName, channelImportance)
|
|
||||||
notificationChannel.description = channelDescription
|
|
||||||
notificationChannel.lightColor = Color.BLUE
|
|
||||||
notificationChannel.lockscreenVisibility = channelLockscreenVisibility
|
|
||||||
// no vibration
|
|
||||||
notificationChannel.vibrationPattern = longArrayOf(0)
|
|
||||||
notificationChannel.enableVibration(channelEnableVibrate)
|
|
||||||
|
|
||||||
// Adds NotificationChannel to system. Attempting to create an existing notification
|
|
||||||
// channel with its original values performs no operation, so it's safe to perform the
|
|
||||||
// below sequence.
|
|
||||||
val notificationManager =
|
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
notificationManager.createNotificationChannel(notificationChannel)
|
|
||||||
}
|
|
||||||
return channelId
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setNotificationBigStyle(notificationData : NotificationVideoModel): NotificationCompat.BigTextStyle {
|
|
||||||
return NotificationCompat.BigTextStyle() // Overrides ContentText in the big form of the template.
|
|
||||||
.bigText(notificationData.mBigText) // Overrides ContentTitle in the big form of the template.
|
|
||||||
.setBigContentTitle(notificationData.mBigContentTitle) // Summary line after the detail section in the big form of the template.
|
|
||||||
// Note: To improve readability, don't overload the user with info. If Summary Text
|
|
||||||
// doesn't add critical information, you should skip it.
|
|
||||||
.setSummaryText(notificationData.mSummaryText)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IMPORTANT NOTE: You should not do this action unless the user takes an action to see your
|
|
||||||
* Notifications like this sample demonstrates. Spamming users to re-enable your notifications
|
|
||||||
* is a bad idea.
|
|
||||||
*/
|
|
||||||
fun openNotificationSettingsForApp(context: Context) {
|
|
||||||
// Links to this app's notification settings.
|
|
||||||
val intent = Intent()
|
|
||||||
intent.action = "android.settings.APP_NOTIFICATION_SETTINGS"
|
|
||||||
intent.putExtra("app_package", context.packageName)
|
|
||||||
intent.putExtra("app_uid", context.applicationInfo.uid)
|
|
||||||
|
|
||||||
// for Android 8 and above
|
|
||||||
intent.putExtra("android.provider.extra.APP_PACKAGE", context.packageName)
|
|
||||||
context.startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,467 +0,0 @@
|
|||||||
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
|
|
||||||
outputMode = 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,379 +0,0 @@
|
|||||||
package com.hmg.hmgDr.util.opentok
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.Resources
|
|
||||||
import android.graphics.PixelFormat
|
|
||||||
import android.opengl.GLES20
|
|
||||||
import android.opengl.GLSurfaceView
|
|
||||||
import android.opengl.Matrix
|
|
||||||
import android.view.View
|
|
||||||
import com.opentok.android.BaseVideoRenderer
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
import java.nio.FloatBuffer
|
|
||||||
import java.nio.ShortBuffer
|
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
|
||||||
import javax.microedition.khronos.egl.EGLConfig
|
|
||||||
import javax.microedition.khronos.opengles.GL10
|
|
||||||
|
|
||||||
/*
|
|
||||||
* https://nhancv.medium.com/android-how-to-make-a-circular-view-as-a-thumbnail-of-opentok-27992aee15c9
|
|
||||||
* to solve make circle video stream
|
|
||||||
* */
|
|
||||||
|
|
||||||
class DynamicVideoRenderer(private val mContext: Context) : BaseVideoRenderer() {
|
|
||||||
private val mView: GLSurfaceView = GLSurfaceView(mContext)
|
|
||||||
private val mRenderer: MyRenderer
|
|
||||||
|
|
||||||
interface DynamicVideoRendererMetadataListener {
|
|
||||||
fun onMetadataReady(metadata: ByteArray?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setDynamicVideoRendererMetadataListener(metadataListener: DynamicVideoRendererMetadataListener?) {
|
|
||||||
mRenderer.metadataListener = metadataListener
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableThumbnailCircle(enable: Boolean) {
|
|
||||||
mRenderer.requestEnableThumbnailCircle = enable
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class MyRenderer : GLSurfaceView.Renderer {
|
|
||||||
var mTextureIds = IntArray(3)
|
|
||||||
var mScaleMatrix = FloatArray(16)
|
|
||||||
private val mVertexBuffer: FloatBuffer
|
|
||||||
private val mTextureBuffer: FloatBuffer
|
|
||||||
private val mDrawListBuffer: ShortBuffer
|
|
||||||
var requestEnableThumbnailCircle = false
|
|
||||||
var mVideoFitEnabled = true
|
|
||||||
var mVideoDisabled = false
|
|
||||||
private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw
|
|
||||||
|
|
||||||
// vertices
|
|
||||||
private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition;
|
|
||||||
attribute vec2 aTextureCoord;
|
|
||||||
varying vec2 vTextureCoord;
|
|
||||||
void main() {
|
|
||||||
gl_Position = uMVPMatrix * aPosition;
|
|
||||||
vTextureCoord = aTextureCoord;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
private val fragmentShaderCode = """precision mediump float;
|
|
||||||
uniform sampler2D Ytex;
|
|
||||||
uniform sampler2D Utex,Vtex;
|
|
||||||
uniform int enableCircle;
|
|
||||||
uniform vec2 radiusDp;
|
|
||||||
varying vec2 vTextureCoord;
|
|
||||||
void main(void) {
|
|
||||||
float nx,ny,r,g,b,y,u,v;
|
|
||||||
mediump vec4 txl,ux,vx; nx=vTextureCoord[0];
|
|
||||||
ny=vTextureCoord[1];
|
|
||||||
y=texture2D(Ytex,vec2(nx,ny)).r;
|
|
||||||
u=texture2D(Utex,vec2(nx,ny)).r;
|
|
||||||
v=texture2D(Vtex,vec2(nx,ny)).r;
|
|
||||||
y=1.1643*(y-0.0625);
|
|
||||||
u=u-0.5;
|
|
||||||
v=v-0.5;
|
|
||||||
r=y+1.5958*v;
|
|
||||||
g=y-0.39173*u-0.81290*v;
|
|
||||||
b=y+2.017*u;
|
|
||||||
if (enableCircle > 0) {
|
|
||||||
float radius = 0.5;
|
|
||||||
vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
|
|
||||||
vec4 color1 = vec4(r, g, b, 1.0);
|
|
||||||
vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5));
|
|
||||||
float t = 1.0;
|
|
||||||
if (dist < 0.0) t = 0.0;
|
|
||||||
gl_FragColor = mix(color0, color1, t);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
gl_FragColor = vec4(r, g, b, 1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
var mFrameLock = ReentrantLock()
|
|
||||||
var mCurrentFrame: Frame? = null
|
|
||||||
private var mProgram = 0
|
|
||||||
private var mTextureWidth = 0
|
|
||||||
private var mTextureHeight = 0
|
|
||||||
private var mViewportWidth = 0
|
|
||||||
private var mViewportHeight = 0
|
|
||||||
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
|
|
||||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
|
||||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
|
||||||
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
|
|
||||||
vertexShaderCode)
|
|
||||||
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
|
|
||||||
fragmentShaderCode)
|
|
||||||
mProgram = GLES20.glCreateProgram() // create empty OpenGL ES
|
|
||||||
// Program
|
|
||||||
GLES20.glAttachShader(mProgram, vertexShader) // add the vertex
|
|
||||||
// shader to program
|
|
||||||
GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment
|
|
||||||
// shader to
|
|
||||||
// program
|
|
||||||
GLES20.glLinkProgram(mProgram)
|
|
||||||
val positionHandle = GLES20.glGetAttribLocation(mProgram,
|
|
||||||
"aPosition")
|
|
||||||
val textureHandle = GLES20.glGetAttribLocation(mProgram,
|
|
||||||
"aTextureCoord")
|
|
||||||
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
|
|
||||||
GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4,
|
|
||||||
mVertexBuffer)
|
|
||||||
GLES20.glEnableVertexAttribArray(positionHandle)
|
|
||||||
GLES20.glVertexAttribPointer(textureHandle,
|
|
||||||
TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
|
|
||||||
TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer)
|
|
||||||
GLES20.glEnableVertexAttribArray(textureHandle)
|
|
||||||
GLES20.glUseProgram(mProgram)
|
|
||||||
var i = GLES20.glGetUniformLocation(mProgram, "Ytex")
|
|
||||||
GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */
|
|
||||||
i = GLES20.glGetUniformLocation(mProgram, "Utex")
|
|
||||||
GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */
|
|
||||||
i = GLES20.glGetUniformLocation(mProgram, "Vtex")
|
|
||||||
GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */
|
|
||||||
val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp")
|
|
||||||
val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt()
|
|
||||||
GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat())
|
|
||||||
mTextureWidth = 0
|
|
||||||
mTextureHeight = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableThumbnailCircle(enable: Boolean) {
|
|
||||||
GLES20.glUseProgram(mProgram)
|
|
||||||
val enableCircleLocation = GLES20.glGetUniformLocation(mProgram, "enableCircle")
|
|
||||||
GLES20.glUniform1i(enableCircleLocation, if (enable) 1 else 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setupTextures(frame: Frame) {
|
|
||||||
if (mTextureIds[0] != 0) {
|
|
||||||
GLES20.glDeleteTextures(3, mTextureIds, 0)
|
|
||||||
}
|
|
||||||
GLES20.glGenTextures(3, mTextureIds, 0)
|
|
||||||
val w = frame.width
|
|
||||||
val h = frame.height
|
|
||||||
val hw = w + 1 shr 1
|
|
||||||
val hh = h + 1 shr 1
|
|
||||||
initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h)
|
|
||||||
initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh)
|
|
||||||
initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh)
|
|
||||||
mTextureWidth = frame.width
|
|
||||||
mTextureHeight = frame.height
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateTextures(frame: Frame) {
|
|
||||||
val width = frame.width
|
|
||||||
val height = frame.height
|
|
||||||
val half_width = width + 1 shr 1
|
|
||||||
val half_height = height + 1 shr 1
|
|
||||||
val y_size = width * height
|
|
||||||
val uv_size = half_width * half_height
|
|
||||||
val bb = frame.buffer
|
|
||||||
// If we are reusing this frame, make sure we reset position and
|
|
||||||
// limit
|
|
||||||
bb.clear()
|
|
||||||
if (bb.remaining() == y_size + uv_size * 2) {
|
|
||||||
bb.position(0)
|
|
||||||
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1)
|
|
||||||
GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1)
|
|
||||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
|
||||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0])
|
|
||||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
|
|
||||||
height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
|
|
||||||
bb)
|
|
||||||
bb.position(y_size)
|
|
||||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
|
|
||||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1])
|
|
||||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
|
||||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
|
||||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
|
||||||
bb.position(y_size + uv_size)
|
|
||||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
|
|
||||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2])
|
|
||||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
|
||||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
|
||||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
|
||||||
} else {
|
|
||||||
mTextureWidth = 0
|
|
||||||
mTextureHeight = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
|
|
||||||
GLES20.glViewport(0, 0, width, height)
|
|
||||||
mViewportWidth = width
|
|
||||||
mViewportHeight = height
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadataListener: DynamicVideoRendererMetadataListener? = null
|
|
||||||
override fun onDrawFrame(gl: GL10) {
|
|
||||||
gl.glClearColor(0f, 0f, 0f, 0f)
|
|
||||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
|
||||||
mFrameLock.lock()
|
|
||||||
if (mCurrentFrame != null && !mVideoDisabled) {
|
|
||||||
GLES20.glUseProgram(mProgram)
|
|
||||||
if (mTextureWidth != mCurrentFrame!!.width
|
|
||||||
|| mTextureHeight != mCurrentFrame!!.height) {
|
|
||||||
setupTextures(mCurrentFrame!!)
|
|
||||||
}
|
|
||||||
updateTextures(mCurrentFrame!!)
|
|
||||||
Matrix.setIdentityM(mScaleMatrix, 0)
|
|
||||||
var scaleX = 1.0f
|
|
||||||
var scaleY = 1.0f
|
|
||||||
val ratio = (mCurrentFrame!!.width.toFloat()
|
|
||||||
/ mCurrentFrame!!.height)
|
|
||||||
val vratio = mViewportWidth.toFloat() / mViewportHeight
|
|
||||||
if (mVideoFitEnabled) {
|
|
||||||
if (ratio > vratio) {
|
|
||||||
scaleY = vratio / ratio
|
|
||||||
} else {
|
|
||||||
scaleX = ratio / vratio
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ratio < vratio) {
|
|
||||||
scaleY = vratio / ratio
|
|
||||||
} else {
|
|
||||||
scaleX = ratio / vratio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Matrix.scaleM(mScaleMatrix, 0,
|
|
||||||
scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f,
|
|
||||||
scaleY, 1f)
|
|
||||||
metadataListener?.onMetadataReady(mCurrentFrame!!.metadata)
|
|
||||||
val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
|
|
||||||
"uMVPMatrix")
|
|
||||||
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false,
|
|
||||||
mScaleMatrix, 0)
|
|
||||||
enableThumbnailCircle(requestEnableThumbnailCircle)
|
|
||||||
GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size,
|
|
||||||
GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer)
|
|
||||||
} else {
|
|
||||||
//black frame when video is disabled
|
|
||||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
|
||||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
|
||||||
}
|
|
||||||
mFrameLock.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun displayFrame(frame: Frame?) {
|
|
||||||
mFrameLock.lock()
|
|
||||||
if (mCurrentFrame != null) {
|
|
||||||
mCurrentFrame!!.recycle()
|
|
||||||
}
|
|
||||||
mCurrentFrame = frame
|
|
||||||
mFrameLock.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disableVideo(b: Boolean) {
|
|
||||||
mFrameLock.lock()
|
|
||||||
mVideoDisabled = b
|
|
||||||
if (mVideoDisabled) {
|
|
||||||
if (mCurrentFrame != null) {
|
|
||||||
mCurrentFrame!!.recycle()
|
|
||||||
}
|
|
||||||
mCurrentFrame = null
|
|
||||||
}
|
|
||||||
mFrameLock.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableVideoFit(enableVideoFit: Boolean) {
|
|
||||||
mVideoFitEnabled = enableVideoFit
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
// number of coordinates per vertex in this array
|
|
||||||
const val COORDS_PER_VERTEX = 3
|
|
||||||
const val TEXTURECOORDS_PER_VERTEX = 2
|
|
||||||
var mXYZCoords = floatArrayOf(
|
|
||||||
-1.0f, 1.0f, 0.0f, // top left
|
|
||||||
-1.0f, -1.0f, 0.0f, // bottom left
|
|
||||||
1.0f, -1.0f, 0.0f, // bottom right
|
|
||||||
1.0f, 1.0f, 0.0f // top right
|
|
||||||
)
|
|
||||||
var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
|
|
||||||
fun initializeTexture(name: Int, id: Int, width: Int, height: Int) {
|
|
||||||
GLES20.glActiveTexture(name)
|
|
||||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
|
|
||||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
|
||||||
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
|
|
||||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
|
||||||
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
|
|
||||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
|
||||||
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
|
||||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
|
||||||
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
|
||||||
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
|
|
||||||
width, height, 0, GLES20.GL_LUMINANCE,
|
|
||||||
GLES20.GL_UNSIGNED_BYTE, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadShader(type: Int, shaderCode: String?): Int {
|
|
||||||
val shader = GLES20.glCreateShader(type)
|
|
||||||
GLES20.glShaderSource(shader, shaderCode)
|
|
||||||
GLES20.glCompileShader(shader)
|
|
||||||
return shader
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4)
|
|
||||||
bb.order(ByteOrder.nativeOrder())
|
|
||||||
mVertexBuffer = bb.asFloatBuffer()
|
|
||||||
mVertexBuffer.put(mXYZCoords)
|
|
||||||
mVertexBuffer.position(0)
|
|
||||||
val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4)
|
|
||||||
tb.order(ByteOrder.nativeOrder())
|
|
||||||
mTextureBuffer = tb.asFloatBuffer()
|
|
||||||
mTextureBuffer.put(mUVCoords)
|
|
||||||
mTextureBuffer.position(0)
|
|
||||||
val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2)
|
|
||||||
dlb.order(ByteOrder.nativeOrder())
|
|
||||||
mDrawListBuffer = dlb.asShortBuffer()
|
|
||||||
mDrawListBuffer.put(mVertexIndex)
|
|
||||||
mDrawListBuffer.position(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFrame(frame: Frame) {
|
|
||||||
mRenderer.displayFrame(frame)
|
|
||||||
mView.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setStyle(key: String, value: String) {
|
|
||||||
if (STYLE_VIDEO_SCALE == key) {
|
|
||||||
if (STYLE_VIDEO_FIT == value) {
|
|
||||||
mRenderer.enableVideoFit(true)
|
|
||||||
} else if (STYLE_VIDEO_FILL == value) {
|
|
||||||
mRenderer.enableVideoFit(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onVideoPropertiesChanged(videoEnabled: Boolean) {
|
|
||||||
mRenderer.disableVideo(!videoEnabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getView(): View {
|
|
||||||
return mView
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
mView.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
mView.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val THUMBNAIL_SIZE = 90 //in dp
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
mView.setEGLContextClientVersion(2)
|
|
||||||
mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
|
|
||||||
mView.holder.setFormat(PixelFormat.TRANSLUCENT)
|
|
||||||
mRenderer = MyRenderer()
|
|
||||||
mView.setRenderer(mRenderer)
|
|
||||||
mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,357 +0,0 @@
|
|||||||
package com.hmg.hmgDr.util.opentok
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.Resources
|
|
||||||
import android.graphics.PixelFormat
|
|
||||||
import android.opengl.GLES20
|
|
||||||
import android.opengl.GLSurfaceView
|
|
||||||
import android.opengl.Matrix
|
|
||||||
import android.view.View
|
|
||||||
import com.opentok.android.BaseVideoRenderer
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
import java.nio.FloatBuffer
|
|
||||||
import java.nio.ShortBuffer
|
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
|
||||||
import javax.microedition.khronos.egl.EGLConfig
|
|
||||||
import javax.microedition.khronos.opengles.GL10
|
|
||||||
|
|
||||||
|
|
||||||
class ThumbnailCircleVideoRenderer(private val mContext: Context) : BaseVideoRenderer() {
|
|
||||||
private val mView: GLSurfaceView = GLSurfaceView(mContext)
|
|
||||||
private val mRenderer: MyRenderer
|
|
||||||
|
|
||||||
interface ThumbnailCircleVideoRendererMetadataListener {
|
|
||||||
fun onMetadataReady(metadata: ByteArray?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setThumbnailCircleVideoRendererMetadataListener(metadataListener: ThumbnailCircleVideoRendererMetadataListener?) {
|
|
||||||
mRenderer.metadataListener = metadataListener
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class MyRenderer : GLSurfaceView.Renderer {
|
|
||||||
var mTextureIds = IntArray(3)
|
|
||||||
var mScaleMatrix = FloatArray(16)
|
|
||||||
private val mVertexBuffer: FloatBuffer
|
|
||||||
private val mTextureBuffer: FloatBuffer
|
|
||||||
private val mDrawListBuffer: ShortBuffer
|
|
||||||
var mVideoFitEnabled = true
|
|
||||||
var mVideoDisabled = false
|
|
||||||
private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw
|
|
||||||
|
|
||||||
// vertices
|
|
||||||
private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition;
|
|
||||||
attribute vec2 aTextureCoord;
|
|
||||||
varying vec2 vTextureCoord;
|
|
||||||
void main() {
|
|
||||||
gl_Position = uMVPMatrix * aPosition;
|
|
||||||
vTextureCoord = aTextureCoord;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
private val fragmentShaderCode = """precision mediump float;
|
|
||||||
uniform sampler2D Ytex;
|
|
||||||
uniform sampler2D Utex,Vtex;
|
|
||||||
uniform vec2 radiusDp;
|
|
||||||
varying vec2 vTextureCoord;
|
|
||||||
void main(void) {
|
|
||||||
float nx,ny,r,g,b,y,u,v;
|
|
||||||
mediump vec4 txl,ux,vx; nx=vTextureCoord[0];
|
|
||||||
ny=vTextureCoord[1];
|
|
||||||
y=texture2D(Ytex,vec2(nx,ny)).r;
|
|
||||||
u=texture2D(Utex,vec2(nx,ny)).r;
|
|
||||||
v=texture2D(Vtex,vec2(nx,ny)).r;
|
|
||||||
y=1.1643*(y-0.0625);
|
|
||||||
u=u-0.5;
|
|
||||||
v=v-0.5;
|
|
||||||
r=y+1.5958*v;
|
|
||||||
g=y-0.39173*u-0.81290*v;
|
|
||||||
b=y+2.017*u;
|
|
||||||
float radius = 0.5;
|
|
||||||
vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
|
|
||||||
vec4 color1 = vec4(r, g, b, 1.0);
|
|
||||||
vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5));
|
|
||||||
float t = 1.0;
|
|
||||||
if (dist < 0.0) t = 0.0;
|
|
||||||
gl_FragColor = mix(color0, color1, t);
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
var mFrameLock = ReentrantLock()
|
|
||||||
var mCurrentFrame: Frame? = null
|
|
||||||
private var mProgram = 0
|
|
||||||
private var mTextureWidth = 0
|
|
||||||
private var mTextureHeight = 0
|
|
||||||
private var mViewportWidth = 0
|
|
||||||
private var mViewportHeight = 0
|
|
||||||
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
|
|
||||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
|
||||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
|
||||||
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
|
|
||||||
vertexShaderCode)
|
|
||||||
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
|
|
||||||
fragmentShaderCode)
|
|
||||||
mProgram = GLES20.glCreateProgram() // create empty OpenGL ES
|
|
||||||
// Program
|
|
||||||
GLES20.glAttachShader(mProgram, vertexShader) // add the vertex
|
|
||||||
// shader to program
|
|
||||||
GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment
|
|
||||||
// shader to
|
|
||||||
// program
|
|
||||||
GLES20.glLinkProgram(mProgram)
|
|
||||||
val positionHandle = GLES20.glGetAttribLocation(mProgram,
|
|
||||||
"aPosition")
|
|
||||||
val textureHandle = GLES20.glGetAttribLocation(mProgram,
|
|
||||||
"aTextureCoord")
|
|
||||||
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
|
|
||||||
GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4,
|
|
||||||
mVertexBuffer)
|
|
||||||
GLES20.glEnableVertexAttribArray(positionHandle)
|
|
||||||
GLES20.glVertexAttribPointer(textureHandle,
|
|
||||||
TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
|
|
||||||
TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer)
|
|
||||||
GLES20.glEnableVertexAttribArray(textureHandle)
|
|
||||||
GLES20.glUseProgram(mProgram)
|
|
||||||
var i = GLES20.glGetUniformLocation(mProgram, "Ytex")
|
|
||||||
GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */
|
|
||||||
i = GLES20.glGetUniformLocation(mProgram, "Utex")
|
|
||||||
GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */
|
|
||||||
i = GLES20.glGetUniformLocation(mProgram, "Vtex")
|
|
||||||
GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */
|
|
||||||
val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp")
|
|
||||||
val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt()
|
|
||||||
GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat())
|
|
||||||
mTextureWidth = 0
|
|
||||||
mTextureHeight = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setupTextures(frame: Frame) {
|
|
||||||
if (mTextureIds[0] != 0) {
|
|
||||||
GLES20.glDeleteTextures(3, mTextureIds, 0)
|
|
||||||
}
|
|
||||||
GLES20.glGenTextures(3, mTextureIds, 0)
|
|
||||||
val w = frame.width
|
|
||||||
val h = frame.height
|
|
||||||
val hw = w + 1 shr 1
|
|
||||||
val hh = h + 1 shr 1
|
|
||||||
initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h)
|
|
||||||
initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh)
|
|
||||||
initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh)
|
|
||||||
mTextureWidth = frame.width
|
|
||||||
mTextureHeight = frame.height
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateTextures(frame: Frame) {
|
|
||||||
val width = frame.width
|
|
||||||
val height = frame.height
|
|
||||||
val half_width = width + 1 shr 1
|
|
||||||
val half_height = height + 1 shr 1
|
|
||||||
val y_size = width * height
|
|
||||||
val uv_size = half_width * half_height
|
|
||||||
val bb = frame.buffer
|
|
||||||
// If we are reusing this frame, make sure we reset position and
|
|
||||||
// limit
|
|
||||||
bb.clear()
|
|
||||||
if (bb.remaining() == y_size + uv_size * 2) {
|
|
||||||
bb.position(0)
|
|
||||||
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1)
|
|
||||||
GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1)
|
|
||||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
|
||||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0])
|
|
||||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
|
|
||||||
height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
|
|
||||||
bb)
|
|
||||||
bb.position(y_size)
|
|
||||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
|
|
||||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1])
|
|
||||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
|
||||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
|
||||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
|
||||||
bb.position(y_size + uv_size)
|
|
||||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
|
|
||||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2])
|
|
||||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
|
||||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
|
||||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
|
||||||
} else {
|
|
||||||
mTextureWidth = 0
|
|
||||||
mTextureHeight = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
|
|
||||||
GLES20.glViewport(0, 0, width, height)
|
|
||||||
mViewportWidth = width
|
|
||||||
mViewportHeight = height
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadataListener: ThumbnailCircleVideoRendererMetadataListener? = null
|
|
||||||
override fun onDrawFrame(gl: GL10) {
|
|
||||||
gl.glClearColor(0f, 0f, 0f, 0f)
|
|
||||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
|
||||||
mFrameLock.lock()
|
|
||||||
if (mCurrentFrame != null && !mVideoDisabled) {
|
|
||||||
GLES20.glUseProgram(mProgram)
|
|
||||||
if (mTextureWidth != mCurrentFrame!!.width
|
|
||||||
|| mTextureHeight != mCurrentFrame!!.height) {
|
|
||||||
setupTextures(mCurrentFrame!!)
|
|
||||||
}
|
|
||||||
updateTextures(mCurrentFrame!!)
|
|
||||||
Matrix.setIdentityM(mScaleMatrix, 0)
|
|
||||||
var scaleX = 1.0f
|
|
||||||
var scaleY = 1.0f
|
|
||||||
val ratio = (mCurrentFrame!!.width.toFloat()
|
|
||||||
/ mCurrentFrame!!.height)
|
|
||||||
val vratio = mViewportWidth.toFloat() / mViewportHeight
|
|
||||||
if (mVideoFitEnabled) {
|
|
||||||
if (ratio > vratio) {
|
|
||||||
scaleY = vratio / ratio
|
|
||||||
} else {
|
|
||||||
scaleX = ratio / vratio
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ratio < vratio) {
|
|
||||||
scaleY = vratio / ratio
|
|
||||||
} else {
|
|
||||||
scaleX = ratio / vratio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Matrix.scaleM(mScaleMatrix, 0,
|
|
||||||
scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f,
|
|
||||||
scaleY, 1f)
|
|
||||||
metadataListener?.onMetadataReady(mCurrentFrame!!.metadata)
|
|
||||||
val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
|
|
||||||
"uMVPMatrix")
|
|
||||||
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false,
|
|
||||||
mScaleMatrix, 0)
|
|
||||||
GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size,
|
|
||||||
GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer)
|
|
||||||
} else {
|
|
||||||
//black frame when video is disabled
|
|
||||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
|
||||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
|
||||||
}
|
|
||||||
mFrameLock.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun displayFrame(frame: Frame?) {
|
|
||||||
mFrameLock.lock()
|
|
||||||
if (mCurrentFrame != null) {
|
|
||||||
mCurrentFrame!!.recycle()
|
|
||||||
}
|
|
||||||
mCurrentFrame = frame
|
|
||||||
mFrameLock.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disableVideo(b: Boolean) {
|
|
||||||
mFrameLock.lock()
|
|
||||||
mVideoDisabled = b
|
|
||||||
if (mVideoDisabled) {
|
|
||||||
if (mCurrentFrame != null) {
|
|
||||||
mCurrentFrame!!.recycle()
|
|
||||||
}
|
|
||||||
mCurrentFrame = null
|
|
||||||
}
|
|
||||||
mFrameLock.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableVideoFit(enableVideoFit: Boolean) {
|
|
||||||
mVideoFitEnabled = enableVideoFit
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
// number of coordinates per vertex in this array
|
|
||||||
const val COORDS_PER_VERTEX = 3
|
|
||||||
const val TEXTURECOORDS_PER_VERTEX = 2
|
|
||||||
var mXYZCoords = floatArrayOf(
|
|
||||||
-1.0f, 1.0f, 0.0f, // top left
|
|
||||||
-1.0f, -1.0f, 0.0f, // bottom left
|
|
||||||
1.0f, -1.0f, 0.0f, // bottom right
|
|
||||||
1.0f, 1.0f, 0.0f // top right
|
|
||||||
)
|
|
||||||
var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
|
|
||||||
fun initializeTexture(name: Int, id: Int, width: Int, height: Int) {
|
|
||||||
GLES20.glActiveTexture(name)
|
|
||||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
|
|
||||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
|
||||||
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
|
|
||||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
|
||||||
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
|
|
||||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
|
||||||
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
|
||||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
|
||||||
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
|
||||||
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
|
|
||||||
width, height, 0, GLES20.GL_LUMINANCE,
|
|
||||||
GLES20.GL_UNSIGNED_BYTE, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadShader(type: Int, shaderCode: String?): Int {
|
|
||||||
val shader = GLES20.glCreateShader(type)
|
|
||||||
GLES20.glShaderSource(shader, shaderCode)
|
|
||||||
GLES20.glCompileShader(shader)
|
|
||||||
return shader
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4)
|
|
||||||
bb.order(ByteOrder.nativeOrder())
|
|
||||||
mVertexBuffer = bb.asFloatBuffer()
|
|
||||||
mVertexBuffer.put(mXYZCoords)
|
|
||||||
mVertexBuffer.position(0)
|
|
||||||
val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4)
|
|
||||||
tb.order(ByteOrder.nativeOrder())
|
|
||||||
mTextureBuffer = tb.asFloatBuffer()
|
|
||||||
mTextureBuffer.put(mUVCoords)
|
|
||||||
mTextureBuffer.position(0)
|
|
||||||
val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2)
|
|
||||||
dlb.order(ByteOrder.nativeOrder())
|
|
||||||
mDrawListBuffer = dlb.asShortBuffer()
|
|
||||||
mDrawListBuffer.put(mVertexIndex)
|
|
||||||
mDrawListBuffer.position(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFrame(frame: Frame) {
|
|
||||||
mRenderer.displayFrame(frame)
|
|
||||||
mView.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setStyle(key: String, value: String) {
|
|
||||||
if (STYLE_VIDEO_SCALE == key) {
|
|
||||||
if (STYLE_VIDEO_FIT == value) {
|
|
||||||
mRenderer.enableVideoFit(true)
|
|
||||||
} else if (STYLE_VIDEO_FILL == value) {
|
|
||||||
mRenderer.enableVideoFit(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onVideoPropertiesChanged(videoEnabled: Boolean) {
|
|
||||||
mRenderer.disableVideo(!videoEnabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getView(): View {
|
|
||||||
return mView
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
mView.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
mView.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val THUMBNAIL_SIZE = 90 //in dp
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
mView.setEGLContextClientVersion(2)
|
|
||||||
mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
|
|
||||||
mView.holder.setFormat(PixelFormat.TRANSLUCENT)
|
|
||||||
mRenderer = MyRenderer()
|
|
||||||
mView.setRenderer(mRenderer)
|
|
||||||
mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
package com.hmg.hmgDr.util
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
import android.widget.EditText
|
|
||||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
|
||||||
import kotlin.math.ceil
|
|
||||||
|
|
||||||
object ViewsUtil {
|
|
||||||
|
|
||||||
/* return status bar height on basis of device display metrics */
|
|
||||||
fun getStatusBarHeight(context: Context): Int {
|
|
||||||
return ceil(
|
|
||||||
(25 * context.resources.displayMetrics.density).toDouble()
|
|
||||||
).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context
|
|
||||||
* @return the Screen height in DP
|
|
||||||
*/
|
|
||||||
fun getHeightDp(context: Context, isInDp: Boolean = false): Float {
|
|
||||||
val displayMetrics: DisplayMetrics = context.resources.displayMetrics
|
|
||||||
return if (isInDp) {
|
|
||||||
displayMetrics.heightPixels / displayMetrics.density
|
|
||||||
} else {
|
|
||||||
displayMetrics.heightPixels.toFloat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context
|
|
||||||
* @return the screnn width in dp
|
|
||||||
*/
|
|
||||||
fun getWidthDp(context: Context, isInDp: Boolean = false): Float {
|
|
||||||
val displayMetrics: DisplayMetrics = context.resources.displayMetrics
|
|
||||||
return if (isInDp) {
|
|
||||||
displayMetrics.widthPixels / displayMetrics.density
|
|
||||||
} else {
|
|
||||||
displayMetrics.widthPixels.toFloat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// code to hide soft keyboard
|
|
||||||
fun hideSoftKeyBoard(context: Context, editBox: EditText?) {
|
|
||||||
val imm = context.getSystemService(FlutterFragmentActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
||||||
imm.hideSoftInputFromWindow(editBox?.windowToken, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// code to show soft keyboard
|
|
||||||
private fun showSoftKeyBoard(context: Context, editBox: EditText?) {
|
|
||||||
val inputMethodManager = context.getSystemService(FlutterFragmentActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
||||||
editBox?.requestFocus()
|
|
||||||
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="oval">
|
|
||||||
|
|
||||||
<solid
|
|
||||||
android:color="@color/remoteBackground"/>
|
|
||||||
|
|
||||||
<stroke android:width="2dp" android:color="@color/text_color" />
|
|
||||||
|
|
||||||
<size
|
|
||||||
android:width="120dp"
|
|
||||||
android:height="120dp"/>
|
|
||||||
</shape>
|
|
||||||
|
Before Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
|
|
||||||
</vector>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
|
|
||||||
</vector>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M12,9c-1.6,0 -3.15,0.25 -4.6,0.72v3.1c0,0.39 -0.23,0.74 -0.56,0.9 -0.98,0.49 -1.87,1.12 -2.66,1.85 -0.18,0.18 -0.43,0.28 -0.7,0.28 -0.28,0 -0.53,-0.11 -0.71,-0.29L0.29,13.08c-0.18,-0.17 -0.29,-0.42 -0.29,-0.7 0,-0.28 0.11,-0.53 0.29,-0.71C3.34,8.78 7.46,7 12,7s8.66,1.78 11.71,4.67c0.18,0.18 0.29,0.43 0.29,0.71 0,0.28 -0.11,0.53 -0.29,0.71l-2.48,2.48c-0.18,0.18 -0.43,0.29 -0.71,0.29 -0.27,0 -0.52,-0.11 -0.7,-0.28 -0.79,-0.74 -1.69,-1.36 -2.67,-1.85 -0.33,-0.16 -0.56,-0.5 -0.56,-0.9v-3.1C15.15,9.25 13.6,9 12,9z"/>
|
|
||||||
</vector>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M19,13H5v-2h14v2z"/>
|
|
||||||
</vector>
|
|
||||||
|
Before Width: | Height: | Size: 16 KiB |
@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<solid android:color="@color/text_color" />
|
|
||||||
<stroke
|
|
||||||
android:width="3dp"
|
|
||||||
android:color="@color/text_color" />
|
|
||||||
<corners android:radius="10dp" />
|
|
||||||
<padding
|
|
||||||
android:bottom="0dp"
|
|
||||||
android:left="0dp"
|
|
||||||
android:right="0dp"
|
|
||||||
android:top="0dp" />
|
|
||||||
</shape>
|
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<corners android:radius="1000dp"/>
|
|
||||||
|
|
||||||
<solid android:color="@color/green_dark"/>
|
|
||||||
|
|
||||||
</shape>
|
|
||||||
|
Before Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.2 KiB |
@ -1,81 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="88dp"
|
|
||||||
android:background="@android:color/holo_blue_dark"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/padding_space_medium">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_icon"
|
|
||||||
android:layout_width="22dp"
|
|
||||||
android:layout_height="22dp"
|
|
||||||
android:src="@mipmap/ic_launcher" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextAppearance.Compat.Notification"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="@dimen/padding_space_small"
|
|
||||||
android:paddingEnd="@dimen/padding_space_small"
|
|
||||||
android:text="HMG Doctor"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="@dimen/text_size_small" />
|
|
||||||
|
|
||||||
<Chronometer
|
|
||||||
android:id="@+id/notify_timer"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
style="@style/TextAppearance.Compat.Notification"
|
|
||||||
android:paddingStart="@dimen/padding_space_small"
|
|
||||||
android:paddingEnd="@dimen/padding_space_small"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="@dimen/text_size_small"
|
|
||||||
android:format="MM:SS"
|
|
||||||
tools:text="25:45" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_Arrow"
|
|
||||||
android:layout_width="22dp"
|
|
||||||
android:layout_height="22dp"
|
|
||||||
android:src="@drawable/ic_arrow_bottom" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/notify_title"
|
|
||||||
style="@style/TextAppearance.Compat.Notification.Title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/padding_space_medium"
|
|
||||||
android:paddingStart="@dimen/padding_space_small"
|
|
||||||
android:paddingEnd="@dimen/padding_space_small"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="@dimen/text_size_small"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="Mosa zaid mosa abuzaid" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/notify_content"
|
|
||||||
style="@style/TextAppearance.Compat.Notification.Title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="@dimen/padding_space_small"
|
|
||||||
android:paddingEnd="@dimen/padding_space_small"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="@dimen/text_size_small"
|
|
||||||
android:text="Tap to return to call" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@android:color/holo_blue_dark"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/padding_space_medium">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_icon"
|
|
||||||
android:layout_width="22dp"
|
|
||||||
android:layout_height="22dp"
|
|
||||||
android:src="@mipmap/ic_launcher" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextAppearance.Compat.Notification"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="@dimen/padding_space_small"
|
|
||||||
android:paddingEnd="@dimen/padding_space_small"
|
|
||||||
android:text="HMG Doctor"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="@dimen/text_size_small" />
|
|
||||||
|
|
||||||
<Chronometer
|
|
||||||
android:id="@+id/notify_timer"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
style="@style/TextAppearance.Compat.Notification"
|
|
||||||
android:paddingStart="@dimen/padding_space_small"
|
|
||||||
android:paddingEnd="@dimen/padding_space_small"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="@dimen/text_size_small"
|
|
||||||
android:format="MM:SS"
|
|
||||||
tools:text="25:45" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_Arrow"
|
|
||||||
android:layout_width="22dp"
|
|
||||||
android:layout_height="22dp"
|
|
||||||
android:src="@drawable/ic_arrow_top" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/notify_title"
|
|
||||||
style="@style/TextAppearance.Compat.Notification.Title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/padding_space_big"
|
|
||||||
android:paddingStart="@dimen/padding_space_small"
|
|
||||||
android:paddingEnd="@dimen/padding_space_small"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="Mosa zaid mosa abuzaid" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/notify_content"
|
|
||||||
style="@style/TextAppearance.Compat.Notification.Title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="@dimen/padding_space_small"
|
|
||||||
android:paddingEnd="@dimen/padding_space_small"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:text="Tap to return to call" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/btn_end"
|
|
||||||
style="@style/TextAppearance.Compat.Notification.Title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/padding_space_medium"
|
|
||||||
android:layout_marginBottom="@dimen/padding_space_medium"
|
|
||||||
android:paddingStart="@dimen/padding_space_small"
|
|
||||||
android:paddingEnd="@dimen/padding_space_small"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:text="End call" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<paths>
|
|
||||||
<external-path name="external_files" path="."/>
|
|
||||||
</paths>
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
#Fri Jun 23 08:50:38 CEST 2017
|
#Sun Jun 13 08:51:58 EEST 2021
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||||
|
|||||||
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 348 KiB |
|
After Width: | Height: | Size: 247 B |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 946 B |
|
After Width: | Height: | Size: 651 B |
|
After Width: | Height: | Size: 655 KiB |
|
After Width: | Height: | Size: 577 KiB |
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 73 74" style="enable-background:new 0 0 73 74;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#E3E3E3;}
|
||||||
|
.st1{fill:none;stroke:#CCCCCC;}
|
||||||
|
.st2{fill:#B4B4B4;}
|
||||||
|
</style>
|
||||||
|
<g id="Ellipse_123-2" transform="translate(2933 475)">
|
||||||
|
<circle class="st0" cx="-2896.5" cy="-437.5" r="35"/>
|
||||||
|
<circle class="st1" cx="-2896.5" cy="-437.5" r="34.5"/>
|
||||||
|
</g>
|
||||||
|
<path class="st2" d="M40.25,43.44c-0.02-0.24-0.04-0.63-0.05-1.04c3.94-0.38,6.7-1.26,6.7-2.29c-0.01,0-0.01-0.04-0.01-0.06
|
||||||
|
c-2.94-2.48,2.55-20.09-7.68-19.73c-0.64-0.51-1.77-0.96-3.38-0.96c-13.86,0.98-7.73,17.61-10.89,20.75c0,0,0,0-0.01,0
|
||||||
|
c0,0,0,0,0,0.01c0,0,0,0,0,0s0,0,0,0c0.01,1.01,2.68,1.87,6.51,2.26c-0.01,0.25-0.03,0.55-0.08,1.06
|
||||||
|
c-1.59,3.99-12.3,2.87-12.8,10.56H53C52.51,46.31,41.84,47.43,40.25,43.44z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,7 @@
|
|||||||
|
<svg id="enter" xmlns="http://www.w3.org/2000/svg" width="42" height="42" viewBox="0 0 42 42">
|
||||||
|
<path id="Path_1146" data-name="Path 1146" d="M17.938,210.625H1.313a1.313,1.313,0,1,1,0-2.625H17.938a1.313,1.313,0,1,1,0,2.625Zm0,0" transform="translate(22.749 -190.938)"/>
|
||||||
|
<path id="Path_1147" data-name="Path 1147" d="M123.984,143.736a1.313,1.313,0,0,1-.928-2.242l5.635-5.635-5.635-5.633a1.313,1.313,0,0,1,1.857-1.857l6.563,6.562a1.313,1.313,0,0,1,0,1.857l-6.562,6.562A1.31,1.31,0,0,1,123.984,143.736Zm0,0" transform="translate(-89.86 -117.486)"/>
|
||||||
|
<path id="Path_1148" data-name="Path 1148" d="M312.668,42.076a3.5,3.5,0,0,0,3.5-3.5V7.076A3.522,3.522,0,0,0,313.8,3.753L303.284.248a3.537,3.537,0,0,0-4.616,3.329v31.5a3.524,3.524,0,0,0,2.368,3.321L311.553,41.9A3.662,3.662,0,0,0,312.668,42.076ZM302.168,2.7a1.03,1.03,0,0,1,.313.046l10.47,3.491a.9.9,0,0,1,.592.838v31.5a.922.922,0,0,1-1.188.83l-10.47-3.491a.905.905,0,0,1-.591-.838V3.576A.876.876,0,0,1,302.168,2.7Zm0,0" transform="translate(-298.668 -0.076)"/>
|
||||||
|
<path id="Path_1149" data-name="Path 1149" d="M195.168,8.75a1.313,1.313,0,0,0,1.313-1.312V4.813A4.816,4.816,0,0,0,191.668,0H171.98a1.313,1.313,0,0,0,0,2.625h19.688a2.19,2.19,0,0,1,2.187,2.188V7.438A1.313,1.313,0,0,0,195.168,8.75Zm0,0" transform="translate(-168.48)"/>
|
||||||
|
<path id="Path_1150" data-name="Path 1150" d="M171.98,350.082h7a4.816,4.816,0,0,0,4.812-4.813v-2.625a1.313,1.313,0,0,0-2.625,0v2.625a2.19,2.19,0,0,1-2.187,2.188h-7a1.313,1.313,0,0,0,0,2.625Zm0,0" transform="translate(-155.793 -313.332)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 753 B |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 246 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 206 B |
|
After Width: | Height: | Size: 987 B |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 409 B |
|
After Width: | Height: | Size: 942 B |
|
After Width: | Height: | Size: 765 B |
|
After Width: | Height: | Size: 232 B |
|
After Width: | Height: | Size: 616 B |
|
After Width: | Height: | Size: 1007 B |
|
After Width: | Height: | Size: 819 B |
|
After Width: | Height: | Size: 631 B |