838 lines
30 KiB
Kotlin
838 lines
30 KiB
Kotlin
package com.simplemobiletools.dialer.activities
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.app.KeyguardManager
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.graphics.Bitmap
|
|
import android.graphics.drawable.LayerDrawable
|
|
import android.graphics.drawable.RippleDrawable
|
|
import android.media.AudioManager
|
|
import android.os.Bundle
|
|
import android.os.Handler
|
|
import android.os.Looper
|
|
import android.os.PowerManager
|
|
import android.telecom.Call
|
|
import android.telecom.CallAudioState
|
|
import android.view.MotionEvent
|
|
import android.view.View
|
|
import android.view.WindowManager
|
|
import android.view.animation.AccelerateDecelerateInterpolator
|
|
import android.view.animation.OvershootInterpolator
|
|
import android.widget.ImageView
|
|
import androidx.core.content.res.ResourcesCompat
|
|
import androidx.core.view.children
|
|
import com.simplemobiletools.commons.extensions.*
|
|
import com.simplemobiletools.commons.helpers.*
|
|
import com.simplemobiletools.commons.models.SimpleListItem
|
|
import com.simplemobiletools.dialer.R
|
|
import com.simplemobiletools.dialer.databinding.ActivityCallBinding
|
|
import com.simplemobiletools.dialer.dialogs.DynamicBottomSheetChooserDialog
|
|
import com.simplemobiletools.dialer.extensions.*
|
|
import com.simplemobiletools.dialer.helpers.*
|
|
import com.simplemobiletools.dialer.models.AudioRoute
|
|
import com.simplemobiletools.dialer.models.CallContact
|
|
import kotlin.math.max
|
|
import kotlin.math.min
|
|
|
|
class CallActivity : SimpleActivity() {
|
|
companion object {
|
|
fun getStartIntent(context: Context): Intent {
|
|
val openAppIntent = Intent(context, CallActivity::class.java)
|
|
openAppIntent.flags = Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
|
|
return openAppIntent
|
|
}
|
|
}
|
|
|
|
private val binding by viewBinding(ActivityCallBinding::inflate)
|
|
|
|
private var isSpeakerOn = false
|
|
private var isMicrophoneOff = false
|
|
private var isCallEnded = false
|
|
private var callContact: CallContact? = null
|
|
private var proximityWakeLock: PowerManager.WakeLock? = null
|
|
private var screenOnWakeLock: PowerManager.WakeLock? = null
|
|
private var callDuration = 0
|
|
private val callContactAvatarHelper by lazy { CallContactAvatarHelper(this) }
|
|
private val callDurationHandler = Handler(Looper.getMainLooper())
|
|
private var dragDownX = 0f
|
|
private var stopAnimation = false
|
|
private var viewsUnderDialpad = arrayListOf<Pair<View, Float>>()
|
|
private var dialpadHeight = 0f
|
|
|
|
private var audioRouteChooserDialog: DynamicBottomSheetChooserDialog? = null
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
setContentView(binding.root)
|
|
|
|
if (CallManager.getPhoneState() == NoCall) {
|
|
finish()
|
|
return
|
|
}
|
|
|
|
updateTextColors(binding.callHolder)
|
|
initButtons()
|
|
audioManager.mode = AudioManager.MODE_IN_CALL
|
|
addLockScreenFlags()
|
|
CallManager.addListener(callCallback)
|
|
updateCallContactInfo(CallManager.getPrimaryCall())
|
|
}
|
|
|
|
override fun onNewIntent(intent: Intent?) {
|
|
super.onNewIntent(intent)
|
|
updateState()
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
updateState()
|
|
updateNavigationBarColor(getProperBackgroundColor())
|
|
|
|
if (config.isUsingSystemTheme) {
|
|
updateStatusbarColor(getProperBackgroundColor())
|
|
}
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
super.onDestroy()
|
|
CallManager.removeListener(callCallback)
|
|
disableProximitySensor()
|
|
|
|
if (screenOnWakeLock?.isHeld == true) {
|
|
screenOnWakeLock!!.release()
|
|
}
|
|
}
|
|
|
|
override fun onBackPressed() {
|
|
if (binding.dialpadWrapper.isVisible()) {
|
|
hideDialpad()
|
|
return
|
|
} else {
|
|
super.onBackPressed()
|
|
}
|
|
|
|
val callState = CallManager.getState()
|
|
if (callState == Call.STATE_CONNECTING || callState == Call.STATE_DIALING) {
|
|
endCall()
|
|
}
|
|
}
|
|
|
|
private fun initButtons() = binding.apply {
|
|
if (config.disableSwipeToAnswer) {
|
|
callDraggable.beGone()
|
|
callDraggableBackground.beGone()
|
|
callLeftArrow.beGone()
|
|
callRightArrow.beGone()
|
|
|
|
callDecline.setOnClickListener {
|
|
endCall()
|
|
}
|
|
|
|
callAccept.setOnClickListener {
|
|
acceptCall()
|
|
}
|
|
} else {
|
|
handleSwipe()
|
|
}
|
|
|
|
callToggleMicrophone.setOnClickListener {
|
|
toggleMicrophone()
|
|
}
|
|
|
|
callToggleSpeaker.setOnClickListener {
|
|
changeCallAudioRoute()
|
|
}
|
|
|
|
callDialpad.setOnClickListener {
|
|
toggleDialpadVisibility()
|
|
}
|
|
|
|
dialpadClose.setOnClickListener {
|
|
hideDialpad()
|
|
}
|
|
|
|
callToggleHold.setOnClickListener {
|
|
toggleHold()
|
|
}
|
|
|
|
callAdd.setOnClickListener {
|
|
Intent(applicationContext, DialpadActivity::class.java).apply {
|
|
addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
|
startActivity(this)
|
|
}
|
|
}
|
|
|
|
callSwap.setOnClickListener {
|
|
CallManager.swap()
|
|
}
|
|
|
|
callMerge.setOnClickListener {
|
|
CallManager.merge()
|
|
}
|
|
|
|
callManage.setOnClickListener {
|
|
startActivity(Intent(this@CallActivity, ConferenceActivity::class.java))
|
|
}
|
|
|
|
callEnd.setOnClickListener {
|
|
endCall()
|
|
}
|
|
|
|
dialpadInclude.apply {
|
|
dialpad0Holder.setOnClickListener { dialpadPressed('0') }
|
|
dialpad1Holder.setOnClickListener { dialpadPressed('1') }
|
|
dialpad2Holder.setOnClickListener { dialpadPressed('2') }
|
|
dialpad3Holder.setOnClickListener { dialpadPressed('3') }
|
|
dialpad4Holder.setOnClickListener { dialpadPressed('4') }
|
|
dialpad5Holder.setOnClickListener { dialpadPressed('5') }
|
|
dialpad6Holder.setOnClickListener { dialpadPressed('6') }
|
|
dialpad7Holder.setOnClickListener { dialpadPressed('7') }
|
|
dialpad8Holder.setOnClickListener { dialpadPressed('8') }
|
|
dialpad9Holder.setOnClickListener { dialpadPressed('9') }
|
|
|
|
arrayOf(
|
|
dialpad0Holder,
|
|
dialpad1Holder,
|
|
dialpad2Holder,
|
|
dialpad3Holder,
|
|
dialpad4Holder,
|
|
dialpad5Holder,
|
|
dialpad6Holder,
|
|
dialpad7Holder,
|
|
dialpad8Holder,
|
|
dialpad9Holder,
|
|
dialpadPlusHolder,
|
|
dialpadAsteriskHolder,
|
|
dialpadHashtagHolder
|
|
).forEach {
|
|
it.background = ResourcesCompat.getDrawable(resources, R.drawable.pill_background, theme)
|
|
it.background?.alpha = LOWER_ALPHA_INT
|
|
}
|
|
|
|
dialpad0Holder.setOnLongClickListener { dialpadPressed('+'); true }
|
|
dialpadAsteriskHolder.setOnClickListener { dialpadPressed('*') }
|
|
dialpadHashtagHolder.setOnClickListener { dialpadPressed('#') }
|
|
}
|
|
|
|
dialpadWrapper.setBackgroundColor(getProperBackgroundColor())
|
|
arrayOf(dialpadClose, callSimImage).forEach {
|
|
it.applyColorFilter(getProperTextColor())
|
|
}
|
|
|
|
val bgColor = getProperBackgroundColor()
|
|
val inactiveColor = getInactiveButtonColor()
|
|
arrayOf(
|
|
callToggleMicrophone, callToggleSpeaker, callDialpad,
|
|
callToggleHold, callAdd, callSwap, callMerge, callManage
|
|
).forEach {
|
|
it.applyColorFilter(bgColor.getContrastColor())
|
|
it.background.applyColorFilter(inactiveColor)
|
|
}
|
|
|
|
arrayOf(
|
|
callToggleMicrophone, callToggleSpeaker, callDialpad,
|
|
callToggleHold, callAdd, callSwap, callMerge, callManage
|
|
).forEach { imageView ->
|
|
imageView.setOnLongClickListener {
|
|
if (!imageView.contentDescription.isNullOrEmpty()) {
|
|
toast(imageView.contentDescription.toString())
|
|
}
|
|
true
|
|
}
|
|
}
|
|
|
|
callSimId.setTextColor(getProperTextColor().getContrastColor())
|
|
dialpadInput.disableKeyboard()
|
|
|
|
dialpadWrapper.onGlobalLayout {
|
|
dialpadHeight = dialpadWrapper.height.toFloat()
|
|
}
|
|
}
|
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
private fun handleSwipe() = binding.apply {
|
|
var minDragX = 0f
|
|
var maxDragX = 0f
|
|
var initialDraggableX = 0f
|
|
var initialLeftArrowX = 0f
|
|
var initialRightArrowX = 0f
|
|
var initialLeftArrowScaleX = 0f
|
|
var initialLeftArrowScaleY = 0f
|
|
var initialRightArrowScaleX = 0f
|
|
var initialRightArrowScaleY = 0f
|
|
var leftArrowTranslation = 0f
|
|
var rightArrowTranslation = 0f
|
|
|
|
val isRtl = isRTLLayout
|
|
callAccept.onGlobalLayout {
|
|
minDragX = if (isRtl) {
|
|
callAccept.left.toFloat()
|
|
} else {
|
|
callDecline.left.toFloat()
|
|
}
|
|
|
|
maxDragX = if (isRtl) {
|
|
callDecline.left.toFloat()
|
|
} else {
|
|
callAccept.left.toFloat()
|
|
}
|
|
|
|
initialDraggableX = callDraggable.left.toFloat()
|
|
initialLeftArrowX = callLeftArrow.x
|
|
initialRightArrowX = callRightArrow.x
|
|
initialLeftArrowScaleX = callLeftArrow.scaleX
|
|
initialLeftArrowScaleY = callLeftArrow.scaleY
|
|
initialRightArrowScaleX = callRightArrow.scaleX
|
|
initialRightArrowScaleY = callRightArrow.scaleY
|
|
leftArrowTranslation = if (isRtl) {
|
|
callAccept.x
|
|
} else {
|
|
-callDecline.x
|
|
}
|
|
|
|
rightArrowTranslation = if (isRtl) {
|
|
-callAccept.x
|
|
} else {
|
|
callDecline.x
|
|
}
|
|
|
|
if (isRtl) {
|
|
callLeftArrow.setImageResource(R.drawable.ic_chevron_right_vector)
|
|
callRightArrow.setImageResource(R.drawable.ic_chevron_left_vector)
|
|
}
|
|
|
|
callLeftArrow.applyColorFilter(getColor(R.color.md_red_400))
|
|
callRightArrow.applyColorFilter(getColor(R.color.md_green_400))
|
|
|
|
startArrowAnimation(callLeftArrow, initialLeftArrowX, initialLeftArrowScaleX, initialLeftArrowScaleY, leftArrowTranslation)
|
|
startArrowAnimation(callRightArrow, initialRightArrowX, initialRightArrowScaleX, initialRightArrowScaleY, rightArrowTranslation)
|
|
}
|
|
|
|
callDraggable.drawable.mutate().setTint(getProperTextColor())
|
|
callDraggableBackground.drawable.mutate().setTint(getProperTextColor())
|
|
|
|
var lock = false
|
|
callDraggable.setOnTouchListener { _, event ->
|
|
when (event.action) {
|
|
MotionEvent.ACTION_DOWN -> {
|
|
dragDownX = event.x
|
|
callDraggableBackground.animate().alpha(0f)
|
|
stopAnimation = true
|
|
callLeftArrow.animate().alpha(0f)
|
|
callRightArrow.animate().alpha(0f)
|
|
lock = false
|
|
}
|
|
|
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
|
dragDownX = 0f
|
|
callDraggable.animate().x(initialDraggableX).withEndAction {
|
|
callDraggableBackground.animate().alpha(0.2f)
|
|
}
|
|
callDraggable.setImageDrawable(getDrawable(R.drawable.ic_phone_down_vector))
|
|
callDraggable.drawable.mutate().setTint(getProperTextColor())
|
|
callLeftArrow.animate().alpha(1f)
|
|
callRightArrow.animate().alpha(1f)
|
|
stopAnimation = false
|
|
startArrowAnimation(callLeftArrow, initialLeftArrowX, initialLeftArrowScaleX, initialLeftArrowScaleY, leftArrowTranslation)
|
|
startArrowAnimation(callRightArrow, initialRightArrowX, initialRightArrowScaleX, initialRightArrowScaleY, rightArrowTranslation)
|
|
}
|
|
MotionEvent.ACTION_MOVE -> {
|
|
callDraggable.x = min(maxDragX, max(minDragX, event.rawX - dragDownX))
|
|
when {
|
|
callDraggable.x >= maxDragX - 50f -> {
|
|
if (!lock) {
|
|
lock = true
|
|
callDraggable.performHapticFeedback()
|
|
if (isRtl) {
|
|
endCall()
|
|
} else {
|
|
acceptCall()
|
|
}
|
|
}
|
|
}
|
|
|
|
callDraggable.x <= minDragX + 50f -> {
|
|
if (!lock) {
|
|
lock = true
|
|
callDraggable.performHapticFeedback()
|
|
if (isRtl) {
|
|
acceptCall()
|
|
} else {
|
|
endCall()
|
|
}
|
|
}
|
|
}
|
|
|
|
callDraggable.x > initialDraggableX -> {
|
|
lock = false
|
|
val drawableRes = if (isRtl) {
|
|
R.drawable.ic_phone_down_red_vector
|
|
} else {
|
|
R.drawable.ic_phone_green_vector
|
|
}
|
|
callDraggable.setImageDrawable(getDrawable(drawableRes))
|
|
}
|
|
|
|
callDraggable.x <= initialDraggableX -> {
|
|
lock = false
|
|
val drawableRes = if (isRtl) {
|
|
R.drawable.ic_phone_green_vector
|
|
} else {
|
|
R.drawable.ic_phone_down_red_vector
|
|
}
|
|
callDraggable.setImageDrawable(getDrawable(drawableRes))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
true
|
|
}
|
|
}
|
|
|
|
private fun startArrowAnimation(arrow: ImageView, initialX: Float, initialScaleX: Float, initialScaleY: Float, translation: Float) {
|
|
arrow.apply {
|
|
alpha = 1f
|
|
x = initialX
|
|
scaleX = initialScaleX
|
|
scaleY = initialScaleY
|
|
animate()
|
|
.alpha(0f)
|
|
.translationX(translation)
|
|
.scaleXBy(-0.5f)
|
|
.scaleYBy(-0.5f)
|
|
.setDuration(1000)
|
|
.withEndAction {
|
|
if (!stopAnimation) {
|
|
startArrowAnimation(this, initialX, initialScaleX, initialScaleY, translation)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun dialpadPressed(char: Char) {
|
|
CallManager.keypad(char)
|
|
binding.dialpadInput.addCharacter(char)
|
|
}
|
|
|
|
private fun changeCallAudioRoute() {
|
|
val supportAudioRoutes = CallManager.getSupportedAudioRoutes()
|
|
if (supportAudioRoutes.contains(AudioRoute.BLUETOOTH)) {
|
|
createOrUpdateAudioRouteChooser(supportAudioRoutes)
|
|
} else {
|
|
val isSpeakerOn = !isSpeakerOn
|
|
val newRoute = if (isSpeakerOn) CallAudioState.ROUTE_SPEAKER else CallAudioState.ROUTE_WIRED_OR_EARPIECE
|
|
CallManager.setAudioRoute(newRoute)
|
|
}
|
|
}
|
|
|
|
private fun createOrUpdateAudioRouteChooser(routes: Array<AudioRoute>, create: Boolean = true) {
|
|
val callAudioRoute = CallManager.getCallAudioRoute()
|
|
val items = routes
|
|
.sortedByDescending { it.route }
|
|
.map {
|
|
SimpleListItem(id = it.route, textRes = it.stringRes, imageRes = it.iconRes, selected = it == callAudioRoute)
|
|
}
|
|
.toTypedArray()
|
|
|
|
if (audioRouteChooserDialog?.isVisible == true) {
|
|
audioRouteChooserDialog?.updateChooserItems(items)
|
|
} else if (create) {
|
|
audioRouteChooserDialog = DynamicBottomSheetChooserDialog.createChooser(
|
|
fragmentManager = supportFragmentManager,
|
|
title = R.string.choose_audio_route,
|
|
items = items
|
|
) {
|
|
audioRouteChooserDialog = null
|
|
CallManager.setAudioRoute(it.id)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun updateCallAudioState(route: AudioRoute?) {
|
|
if (route != null) {
|
|
isMicrophoneOff = audioManager.isMicrophoneMute
|
|
updateMicrophoneButton()
|
|
|
|
isSpeakerOn = route == AudioRoute.SPEAKER
|
|
val supportedAudioRoutes = CallManager.getSupportedAudioRoutes()
|
|
binding.callToggleSpeaker.apply {
|
|
val bluetoothConnected = supportedAudioRoutes.contains(AudioRoute.BLUETOOTH)
|
|
contentDescription = if (bluetoothConnected) {
|
|
getString(R.string.choose_audio_route)
|
|
} else {
|
|
getString(if (isSpeakerOn) R.string.turn_speaker_off else R.string.turn_speaker_on)
|
|
}
|
|
// show speaker icon when a headset is connected, a headset icon maybe confusing to some
|
|
if (route == AudioRoute.WIRED_HEADSET) {
|
|
setImageResource(R.drawable.ic_volume_down_vector)
|
|
} else {
|
|
setImageResource(route.iconRes)
|
|
}
|
|
}
|
|
toggleButtonColor(binding.callToggleSpeaker, enabled = route != AudioRoute.EARPIECE && route != AudioRoute.WIRED_HEADSET)
|
|
createOrUpdateAudioRouteChooser(supportedAudioRoutes, create = false)
|
|
|
|
if (isSpeakerOn) {
|
|
disableProximitySensor()
|
|
} else {
|
|
enableProximitySensor()
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun toggleMicrophone() {
|
|
isMicrophoneOff = !isMicrophoneOff
|
|
audioManager.isMicrophoneMute = isMicrophoneOff
|
|
CallManager.inCallService?.setMuted(isMicrophoneOff)
|
|
updateMicrophoneButton()
|
|
}
|
|
|
|
private fun updateMicrophoneButton() {
|
|
toggleButtonColor(binding.callToggleMicrophone, isMicrophoneOff)
|
|
binding.callToggleMicrophone.contentDescription = getString(if (isMicrophoneOff) R.string.turn_microphone_on else R.string.turn_microphone_off)
|
|
}
|
|
|
|
private fun toggleDialpadVisibility() {
|
|
if (binding.dialpadWrapper.isVisible()) hideDialpad() else showDialpad()
|
|
}
|
|
|
|
private fun findVisibleViewsUnderDialpad(): Sequence<Pair<View, Float>> {
|
|
return binding.ongoingCallHolder.children.filter { it.isVisible() }.map { view -> Pair(view, view.alpha) }
|
|
}
|
|
|
|
private fun showDialpad() {
|
|
binding.dialpadWrapper.apply {
|
|
translationY = dialpadHeight
|
|
alpha = 0f
|
|
animate()
|
|
.withStartAction { beVisible() }
|
|
.setInterpolator(AccelerateDecelerateInterpolator())
|
|
.setDuration(200L)
|
|
.alpha(1f)
|
|
.translationY(0f)
|
|
.start()
|
|
}
|
|
|
|
viewsUnderDialpad.clear()
|
|
viewsUnderDialpad.addAll(findVisibleViewsUnderDialpad())
|
|
viewsUnderDialpad.forEach { (view, _) ->
|
|
view.run {
|
|
animate().scaleX(0f).alpha(0f).withEndAction { beGone() }.duration = 250L
|
|
animate().scaleY(0f).alpha(0f).withEndAction { beGone() }.duration = 250L
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun hideDialpad() {
|
|
binding.dialpadWrapper.animate()
|
|
.withEndAction { binding.dialpadWrapper.beGone() }
|
|
.setInterpolator(AccelerateDecelerateInterpolator())
|
|
.setDuration(200L)
|
|
.alpha(0f)
|
|
.translationY(dialpadHeight)
|
|
.start()
|
|
|
|
viewsUnderDialpad.forEach { (view, alpha) ->
|
|
view.run {
|
|
animate().withStartAction { beVisible() }.setInterpolator(OvershootInterpolator()).scaleX(1f).alpha(alpha).duration = 250L
|
|
animate().withStartAction { beVisible() }.setInterpolator(OvershootInterpolator()).scaleY(1f).alpha(alpha).duration = 250L
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun toggleHold() {
|
|
val isOnHold = CallManager.toggleHold()
|
|
toggleButtonColor(binding.callToggleHold, isOnHold)
|
|
binding.callToggleHold.contentDescription = getString(if (isOnHold) R.string.resume_call else R.string.hold_call)
|
|
binding.holdStatusLabel.beVisibleIf(isOnHold)
|
|
}
|
|
|
|
private fun updateOtherPersonsInfo(avatar: Bitmap?) {
|
|
if (callContact == null) {
|
|
return
|
|
}
|
|
|
|
binding.apply {
|
|
callerNameLabel.text = if (callContact!!.name.isNotEmpty()) callContact!!.name else getString(R.string.unknown_caller)
|
|
if (callContact!!.number.isNotEmpty() && callContact!!.number != callContact!!.name) {
|
|
callerNumber.text = callContact!!.number
|
|
|
|
if (callContact!!.numberLabel.isNotEmpty()) {
|
|
callerNumber.text = "${callContact!!.number} - ${callContact!!.numberLabel}"
|
|
}
|
|
} else {
|
|
callerNumber.beGone()
|
|
}
|
|
|
|
if (avatar != null) {
|
|
callerAvatar.setImageBitmap(avatar)
|
|
} else {
|
|
callerAvatar.setImageDrawable(null)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun getContactNameOrNumber(contact: CallContact): String {
|
|
return contact.name.ifEmpty {
|
|
contact.number.ifEmpty {
|
|
getString(R.string.unknown_caller)
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressLint("MissingPermission")
|
|
private fun checkCalledSIMCard() {
|
|
try {
|
|
val accounts = telecomManager.callCapablePhoneAccounts
|
|
if (accounts.size > 1) {
|
|
accounts.forEachIndexed { index, account ->
|
|
if (account == CallManager.getPrimaryCall()?.details?.accountHandle) {
|
|
binding.apply {
|
|
callSimId.text = "${index + 1}"
|
|
callSimId.beVisible()
|
|
callSimImage.beVisible()
|
|
}
|
|
|
|
val acceptDrawableId = when (index) {
|
|
0 -> R.drawable.ic_phone_one_vector
|
|
1 -> R.drawable.ic_phone_two_vector
|
|
else -> R.drawable.ic_phone_vector
|
|
}
|
|
|
|
val rippleBg = resources.getDrawable(R.drawable.ic_call_accept, theme) as RippleDrawable
|
|
val layerDrawable = rippleBg.findDrawableByLayerId(R.id.accept_call_background_holder) as LayerDrawable
|
|
layerDrawable.setDrawableByLayerId(R.id.accept_call_icon, getDrawable(acceptDrawableId))
|
|
binding.callAccept.setImageDrawable(rippleBg)
|
|
}
|
|
}
|
|
}
|
|
} catch (ignored: Exception) {
|
|
}
|
|
}
|
|
|
|
private fun updateCallState(call: Call) {
|
|
val state = call.getStateCompat()
|
|
when (state) {
|
|
Call.STATE_RINGING -> callRinging()
|
|
Call.STATE_ACTIVE -> callStarted()
|
|
Call.STATE_DISCONNECTED -> endCall()
|
|
Call.STATE_CONNECTING, Call.STATE_DIALING -> initOutgoingCallUI()
|
|
Call.STATE_SELECT_PHONE_ACCOUNT -> showPhoneAccountPicker()
|
|
}
|
|
|
|
val statusTextId = when (state) {
|
|
Call.STATE_RINGING -> R.string.is_calling
|
|
Call.STATE_CONNECTING, Call.STATE_DIALING -> R.string.dialing
|
|
else -> 0
|
|
}
|
|
|
|
binding.apply {
|
|
if (statusTextId != 0) {
|
|
callStatusLabel.text = getString(statusTextId)
|
|
}
|
|
|
|
callManage.beVisibleIf(call.hasCapability(Call.Details.CAPABILITY_MANAGE_CONFERENCE))
|
|
setActionButtonEnabled(callSwap, state == Call.STATE_ACTIVE)
|
|
setActionButtonEnabled(callMerge, state == Call.STATE_ACTIVE)
|
|
}
|
|
}
|
|
|
|
private fun updateState() {
|
|
val phoneState = CallManager.getPhoneState()
|
|
if (phoneState is SingleCall) {
|
|
updateCallState(phoneState.call)
|
|
updateCallOnHoldState(null)
|
|
val state = phoneState.call.getStateCompat()
|
|
val isSingleCallActionsEnabled = (state == Call.STATE_ACTIVE || state == Call.STATE_DISCONNECTED
|
|
|| state == Call.STATE_DISCONNECTING || state == Call.STATE_HOLDING)
|
|
setActionButtonEnabled(binding.callToggleHold, isSingleCallActionsEnabled)
|
|
setActionButtonEnabled(binding.callAdd, isSingleCallActionsEnabled)
|
|
} else if (phoneState is TwoCalls) {
|
|
updateCallState(phoneState.active)
|
|
updateCallOnHoldState(phoneState.onHold)
|
|
}
|
|
|
|
updateCallAudioState(CallManager.getCallAudioRoute())
|
|
}
|
|
|
|
private fun updateCallOnHoldState(call: Call?) {
|
|
val hasCallOnHold = call != null
|
|
if (hasCallOnHold) {
|
|
getCallContact(applicationContext, call) { contact ->
|
|
runOnUiThread {
|
|
binding.onHoldCallerName.text = getContactNameOrNumber(contact)
|
|
}
|
|
}
|
|
}
|
|
binding.apply {
|
|
onHoldStatusHolder.beVisibleIf(hasCallOnHold)
|
|
controlsSingleCall.beVisibleIf(!hasCallOnHold)
|
|
controlsTwoCalls.beVisibleIf(hasCallOnHold)
|
|
}
|
|
}
|
|
|
|
private fun updateCallContactInfo(call: Call?) {
|
|
getCallContact(applicationContext, call) { contact ->
|
|
if (call != CallManager.getPrimaryCall()) {
|
|
return@getCallContact
|
|
}
|
|
callContact = contact
|
|
val avatar = if (!call.isConference()) callContactAvatarHelper.getCallContactAvatar(contact) else null
|
|
runOnUiThread {
|
|
updateOtherPersonsInfo(avatar)
|
|
checkCalledSIMCard()
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun acceptCall() {
|
|
CallManager.accept()
|
|
}
|
|
|
|
private fun initOutgoingCallUI() {
|
|
enableProximitySensor()
|
|
binding.incomingCallHolder.beGone()
|
|
binding.ongoingCallHolder.beVisible()
|
|
}
|
|
|
|
private fun callRinging() {
|
|
binding.incomingCallHolder.beVisible()
|
|
}
|
|
|
|
private fun callStarted() {
|
|
enableProximitySensor()
|
|
binding.incomingCallHolder.beGone()
|
|
binding.ongoingCallHolder.beVisible()
|
|
callDurationHandler.removeCallbacks(updateCallDurationTask)
|
|
callDurationHandler.post(updateCallDurationTask)
|
|
}
|
|
|
|
private fun showPhoneAccountPicker() {
|
|
if (callContact != null) {
|
|
getHandleToUse(intent, callContact!!.number) { handle ->
|
|
CallManager.getPrimaryCall()?.phoneAccountSelected(handle, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun endCall() {
|
|
CallManager.reject()
|
|
disableProximitySensor()
|
|
audioRouteChooserDialog?.dismissAllowingStateLoss()
|
|
|
|
if (isCallEnded) {
|
|
finishAndRemoveTask()
|
|
return
|
|
}
|
|
|
|
try {
|
|
audioManager.mode = AudioManager.MODE_NORMAL
|
|
} catch (ignored: Exception) {
|
|
}
|
|
|
|
isCallEnded = true
|
|
if (callDuration > 0) {
|
|
runOnUiThread {
|
|
binding.callStatusLabel.text = "${callDuration.getFormattedDuration()} (${getString(R.string.call_ended)})"
|
|
Handler().postDelayed({
|
|
finishAndRemoveTask()
|
|
}, 3000)
|
|
}
|
|
} else {
|
|
binding.callStatusLabel.text = getString(R.string.call_ended)
|
|
finish()
|
|
}
|
|
}
|
|
|
|
private val callCallback = object : CallManagerListener {
|
|
override fun onStateChanged() {
|
|
updateState()
|
|
}
|
|
|
|
override fun onAudioStateChanged(audioState: AudioRoute) {
|
|
updateCallAudioState(audioState)
|
|
}
|
|
|
|
override fun onPrimaryCallChanged(call: Call) {
|
|
callDurationHandler.removeCallbacks(updateCallDurationTask)
|
|
updateCallContactInfo(call)
|
|
updateState()
|
|
}
|
|
}
|
|
|
|
private val updateCallDurationTask = object : Runnable {
|
|
override fun run() {
|
|
callDuration = CallManager.getPrimaryCall().getCallDuration()
|
|
if (!isCallEnded) {
|
|
binding.callStatusLabel.text = callDuration.getFormattedDuration()
|
|
callDurationHandler.postDelayed(this, 1000)
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressLint("NewApi")
|
|
private fun addLockScreenFlags() {
|
|
if (isOreoMr1Plus()) {
|
|
setShowWhenLocked(true)
|
|
setTurnScreenOn(true)
|
|
} else {
|
|
window.addFlags(
|
|
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
|
|
or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
|
|
or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
|
|
or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
|
)
|
|
}
|
|
|
|
if (isOreoPlus()) {
|
|
(getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager).requestDismissKeyguard(this, null)
|
|
} else {
|
|
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD)
|
|
}
|
|
|
|
try {
|
|
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
|
|
screenOnWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "com.simplemobiletools.dialer.pro:full_wake_lock")
|
|
screenOnWakeLock!!.acquire(5 * 1000L)
|
|
} catch (e: Exception) {
|
|
}
|
|
}
|
|
|
|
private fun enableProximitySensor() {
|
|
if (!config.disableProximitySensor && (proximityWakeLock == null || proximityWakeLock?.isHeld == false)) {
|
|
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
|
|
proximityWakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "com.simplemobiletools.dialer.pro:wake_lock")
|
|
proximityWakeLock!!.acquire(60 * MINUTE_SECONDS * 1000L)
|
|
}
|
|
}
|
|
|
|
private fun disableProximitySensor() {
|
|
if (proximityWakeLock?.isHeld == true) {
|
|
proximityWakeLock!!.release()
|
|
}
|
|
}
|
|
|
|
private fun setActionButtonEnabled(button: ImageView, enabled: Boolean) {
|
|
button.apply {
|
|
isEnabled = enabled
|
|
alpha = if (enabled) 1.0f else LOWER_ALPHA
|
|
}
|
|
}
|
|
|
|
private fun getActiveButtonColor() = getProperPrimaryColor()
|
|
|
|
private fun getInactiveButtonColor() = getProperTextColor().adjustAlpha(0.10f)
|
|
|
|
private fun toggleButtonColor(view: ImageView, enabled: Boolean) {
|
|
if (enabled) {
|
|
val color = getActiveButtonColor()
|
|
view.background.applyColorFilter(color)
|
|
view.applyColorFilter(color.getContrastColor())
|
|
} else {
|
|
view.background.applyColorFilter(getInactiveButtonColor())
|
|
view.applyColorFilter(getProperBackgroundColor().getContrastColor())
|
|
}
|
|
}
|
|
}
|