working on videoCall notification

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save