Merge pull request #232 from KryptKode/feat/back-press

Feat/ handle back press for calls
This commit is contained in:
Tibor Kaputa
2021-09-25 21:32:59 +02:00
committed by GitHub
7 changed files with 249 additions and 155 deletions

View File

@ -2,8 +2,10 @@ package com.simplemobiletools.dialer
import android.app.Application import android.app.Application
import com.simplemobiletools.commons.extensions.checkUseEnglish import com.simplemobiletools.commons.extensions.checkUseEnglish
import com.simplemobiletools.dialer.helpers.CallDurationHelper
class App : Application() { class App : Application() {
val callDurationHelper by lazy { CallDurationHelper() }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
checkUseEnglish() checkUseEnglish()

View File

@ -1,50 +1,50 @@
package com.simplemobiletools.dialer.activities package com.simplemobiletools.dialer.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.* import android.app.KeyguardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.* import android.graphics.Bitmap
import android.media.AudioManager import android.media.AudioManager
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper
import android.os.PowerManager import android.os.PowerManager
import android.provider.MediaStore
import android.telecom.Call import android.telecom.Call
import android.telecom.CallAudioState import android.telecom.CallAudioState
import android.util.Size
import android.view.WindowManager import android.view.WindowManager
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.MINUTE_SECONDS
import com.simplemobiletools.commons.helpers.isOreoMr1Plus
import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.dialer.App
import com.simplemobiletools.dialer.R import com.simplemobiletools.dialer.R
import com.simplemobiletools.dialer.extensions.addCharacter import com.simplemobiletools.dialer.extensions.addCharacter
import com.simplemobiletools.dialer.extensions.audioManager import com.simplemobiletools.dialer.extensions.audioManager
import com.simplemobiletools.dialer.extensions.config import com.simplemobiletools.dialer.extensions.config
import com.simplemobiletools.dialer.extensions.getHandleToUse import com.simplemobiletools.dialer.extensions.getHandleToUse
import com.simplemobiletools.dialer.helpers.ACCEPT_CALL import com.simplemobiletools.dialer.helpers.CallContactAvatarHelper
import com.simplemobiletools.dialer.helpers.CallManager import com.simplemobiletools.dialer.helpers.CallManager
import com.simplemobiletools.dialer.helpers.DECLINE_CALL
import com.simplemobiletools.dialer.models.CallContact import com.simplemobiletools.dialer.models.CallContact
import com.simplemobiletools.dialer.receivers.CallActionReceiver
import kotlinx.android.synthetic.main.activity_call.* import kotlinx.android.synthetic.main.activity_call.*
import kotlinx.android.synthetic.main.dialpad.* import kotlinx.android.synthetic.main.dialpad.*
import java.util.*
class CallActivity : SimpleActivity() { class CallActivity : SimpleActivity() {
private val CALL_NOTIFICATION_ID = 1 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
return openAppIntent
}
}
private var isSpeakerOn = false private var isSpeakerOn = false
private var isMicrophoneOn = true private var isMicrophoneOn = true
private var isCallEnded = false private var isCallEnded = false
private var callDuration = 0
private var callContact: CallContact? = null private var callContact: CallContact? = null
private var callContactAvatar: Bitmap? = null
private var proximityWakeLock: PowerManager.WakeLock? = null private var proximityWakeLock: PowerManager.WakeLock? = null
private var callTimer = Timer() private var callDuration = 0
private val callContactAvatarHelper by lazy { CallContactAvatarHelper(this) }
private val callDurationHelper by lazy { (application as App).callDurationHelper }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
supportActionBar?.hide() supportActionBar?.hide()
@ -58,10 +58,9 @@ class CallActivity : SimpleActivity() {
CallManager.getCallContact(applicationContext) { contact -> CallManager.getCallContact(applicationContext) { contact ->
callContact = contact callContact = contact
callContactAvatar = getCallContactAvatar() val avatar = callContactAvatarHelper.getCallContactAvatar(contact)
runOnUiThread { runOnUiThread {
setupNotification() updateOtherPersonsInfo(avatar)
updateOtherPersonsInfo()
checkCalledSIMCard() checkCalledSIMCard()
} }
} }
@ -74,14 +73,10 @@ class CallActivity : SimpleActivity() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
notificationManager.cancel(CALL_NOTIFICATION_ID)
CallManager.unregisterCallback(callCallback) CallManager.unregisterCallback(callCallback)
callTimer.cancel()
if (proximityWakeLock?.isHeld == true) { if (proximityWakeLock?.isHeld == true) {
proximityWakeLock!!.release() proximityWakeLock!!.release()
} }
endCall()
} }
override fun onBackPressed() { override fun onBackPressed() {
@ -92,7 +87,8 @@ class CallActivity : SimpleActivity() {
super.onBackPressed() super.onBackPressed()
} }
if (CallManager.getState() == Call.STATE_DIALING) { val callState = CallManager.getState()
if (callState == Call.STATE_CONNECTING || callState == Call.STATE_DIALING) {
endCall() endCall()
} }
} }
@ -180,7 +176,7 @@ class CallActivity : SimpleActivity() {
} }
} }
private fun updateOtherPersonsInfo() { private fun updateOtherPersonsInfo(avatar: Bitmap?) {
if (callContact == null) { if (callContact == null) {
return return
} }
@ -192,8 +188,8 @@ class CallActivity : SimpleActivity() {
caller_number_label.beGone() caller_number_label.beGone()
} }
if (callContactAvatar != null) { if (avatar != null) {
caller_avatar.setImageBitmap(callContactAvatar) caller_avatar.setImageBitmap(avatar)
} }
} }
@ -223,10 +219,6 @@ class CallActivity : SimpleActivity() {
Call.STATE_SELECT_PHONE_ACCOUNT -> showPhoneAccountPicker() Call.STATE_SELECT_PHONE_ACCOUNT -> showPhoneAccountPicker()
} }
if (state == Call.STATE_DISCONNECTED || state == Call.STATE_DISCONNECTING) {
callTimer.cancel()
}
val statusTextId = when (state) { val statusTextId = when (state) {
Call.STATE_RINGING -> R.string.is_calling Call.STATE_RINGING -> R.string.is_calling
Call.STATE_DIALING -> R.string.dialing Call.STATE_DIALING -> R.string.dialing
@ -236,8 +228,6 @@ class CallActivity : SimpleActivity() {
if (statusTextId != 0) { if (statusTextId != 0) {
call_status_label.text = getString(statusTextId) call_status_label.text = getString(statusTextId)
} }
setupNotification()
} }
private fun acceptCall() { private fun acceptCall() {
@ -258,9 +248,13 @@ class CallActivity : SimpleActivity() {
initProximitySensor() initProximitySensor()
incoming_call_holder.beGone() incoming_call_holder.beGone()
ongoing_call_holder.beVisible() ongoing_call_holder.beVisible()
try { callDurationHelper.onDurationChange {
callTimer.scheduleAtFixedRate(getCallTimerUpdateTask(), 1000, 1000) callDuration = it
} catch (ignored: Exception) { runOnUiThread {
if (!isCallEnded) {
call_status_label.text = callDuration.getFormattedDuration()
}
}
} }
} }
@ -302,17 +296,6 @@ class CallActivity : SimpleActivity() {
} }
} }
private fun getCallTimerUpdateTask() = object : TimerTask() {
override fun run() {
callDuration++
runOnUiThread {
if (!isCallEnded) {
call_status_label.text = callDuration.getFormattedDuration()
}
}
}
}
private val callCallback = object : Call.Callback() { private val callCallback = object : Call.Callback() {
override fun onStateChanged(call: Call, state: Int) { override fun onStateChanged(call: Call, state: Int) {
super.onStateChanged(call, state) super.onStateChanged(call, state)
@ -348,106 +331,4 @@ class CallActivity : SimpleActivity() {
proximityWakeLock!!.acquire(10 * MINUTE_SECONDS * 1000L) proximityWakeLock!!.acquire(10 * MINUTE_SECONDS * 1000L)
} }
} }
@SuppressLint("NewApi")
private fun setupNotification() {
val callState = CallManager.getState()
val channelId = "simple_dialer_call"
if (isOreoPlus()) {
val importance = NotificationManager.IMPORTANCE_DEFAULT
val name = "call_notification_channel"
NotificationChannel(channelId, name, importance).apply {
setSound(null, null)
notificationManager.createNotificationChannel(this)
}
}
val openAppIntent = Intent(this, CallActivity::class.java)
openAppIntent.flags = Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
val openAppPendingIntent = PendingIntent.getActivity(this, 0, openAppIntent, 0)
val acceptCallIntent = Intent(this, CallActionReceiver::class.java)
acceptCallIntent.action = ACCEPT_CALL
val acceptPendingIntent = PendingIntent.getBroadcast(this, 0, acceptCallIntent, PendingIntent.FLAG_CANCEL_CURRENT)
val declineCallIntent = Intent(this, CallActionReceiver::class.java)
declineCallIntent.action = DECLINE_CALL
val declinePendingIntent = PendingIntent.getBroadcast(this, 1, declineCallIntent, PendingIntent.FLAG_CANCEL_CURRENT)
val callerName = if (callContact != null && callContact!!.name.isNotEmpty()) callContact!!.name else getString(R.string.unknown_caller)
val contentTextId = when (callState) {
Call.STATE_RINGING -> R.string.is_calling
Call.STATE_DIALING -> R.string.dialing
Call.STATE_DISCONNECTED -> R.string.call_ended
Call.STATE_DISCONNECTING -> R.string.call_ending
else -> R.string.ongoing_call
}
val collapsedView = RemoteViews(packageName, R.layout.call_notification).apply {
setText(R.id.notification_caller_name, callerName)
setText(R.id.notification_call_status, getString(contentTextId))
setVisibleIf(R.id.notification_accept_call, callState == Call.STATE_RINGING)
setOnClickPendingIntent(R.id.notification_decline_call, declinePendingIntent)
setOnClickPendingIntent(R.id.notification_accept_call, acceptPendingIntent)
if (callContactAvatar != null) {
setImageViewBitmap(R.id.notification_thumbnail, getCircularBitmap(callContactAvatar!!))
}
}
val builder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_phone_vector)
.setContentIntent(openAppPendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(Notification.CATEGORY_CALL)
.setCustomContentView(collapsedView)
.setOngoing(true)
.setSound(null)
.setUsesChronometer(callState == Call.STATE_ACTIVE)
.setChannelId(channelId)
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
val notification = builder.build()
notificationManager.notify(CALL_NOTIFICATION_ID, notification)
}
@SuppressLint("NewApi")
private fun getCallContactAvatar(): Bitmap? {
var bitmap: Bitmap? = null
if (callContact?.photoUri?.isNotEmpty() == true) {
val photoUri = Uri.parse(callContact!!.photoUri)
try {
bitmap = if (isQPlus()) {
val tmbSize = resources.getDimension(R.dimen.list_avatar_size).toInt()
contentResolver.loadThumbnail(photoUri, Size(tmbSize, tmbSize), null)
} else {
MediaStore.Images.Media.getBitmap(contentResolver, photoUri)
}
bitmap = getCircularBitmap(bitmap!!)
} catch (ignored: Exception) {
return null
}
}
return bitmap
}
private fun getCircularBitmap(bitmap: Bitmap): Bitmap {
val output = Bitmap.createBitmap(bitmap.width, bitmap.width, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
val paint = Paint()
val rect = Rect(0, 0, bitmap.width, bitmap.height)
val radius = bitmap.width / 2.toFloat()
paint.isAntiAlias = true
canvas.drawARGB(0, 0, 0, 0)
canvas.drawCircle(radius, radius, radius, paint)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(bitmap, rect, rect, paint)
return output
}
} }

View File

@ -0,0 +1,49 @@
package com.simplemobiletools.dialer.helpers
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.net.Uri
import android.provider.MediaStore
import android.util.Size
import com.simplemobiletools.commons.helpers.isQPlus
import com.simplemobiletools.dialer.R
import com.simplemobiletools.dialer.models.CallContact
class CallContactAvatarHelper(private val context: Context) {
@SuppressLint("NewApi")
fun getCallContactAvatar(callContact: CallContact?): Bitmap? {
var bitmap: Bitmap? = null
if (callContact?.photoUri?.isNotEmpty() == true) {
val photoUri = Uri.parse(callContact.photoUri)
try {
val contentResolver = context.contentResolver
bitmap = if (isQPlus()) {
val tmbSize = context.resources.getDimension(R.dimen.list_avatar_size).toInt()
contentResolver.loadThumbnail(photoUri, Size(tmbSize, tmbSize), null)
} else {
MediaStore.Images.Media.getBitmap(contentResolver, photoUri)
}
bitmap = getCircularBitmap(bitmap!!)
} catch (ignored: Exception) {
return null
}
}
return bitmap
}
fun getCircularBitmap(bitmap: Bitmap): Bitmap {
val output = Bitmap.createBitmap(bitmap.width, bitmap.width, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
val paint = Paint()
val rect = Rect(0, 0, bitmap.width, bitmap.height)
val radius = bitmap.width / 2.toFloat()
paint.isAntiAlias = true
canvas.drawARGB(0, 0, 0, 0)
canvas.drawCircle(radius, radius, radius, paint)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(bitmap, rect, rect, paint)
return output
}
}

View File

@ -0,0 +1,34 @@
package com.simplemobiletools.dialer.helpers
import java.util.Timer
import java.util.TimerTask
class CallDurationHelper {
private var callTimer: Timer? = null
private var callDuration = 0
private var callback: ((durationSecs: Int) -> Unit)? = null
fun onDurationChange(callback: (durationSecs: Int) -> Unit) {
this.callback = callback
}
fun start() {
try {
callDuration = 0
callTimer = Timer()
callTimer?.scheduleAtFixedRate(getTimerUpdateTask(), 1000, 1000)
} catch (ignored: Exception) {
}
}
fun cancel() {
callTimer?.cancel()
}
private fun getTimerUpdateTask() = object : TimerTask() {
override fun run() {
callDuration++
callback?.invoke(callDuration)
}
}
}

View File

@ -0,0 +1,97 @@
package com.simplemobiletools.dialer.helpers
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.telecom.Call
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import com.simplemobiletools.commons.extensions.notificationManager
import com.simplemobiletools.commons.extensions.setText
import com.simplemobiletools.commons.extensions.setVisibleIf
import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.dialer.R
import com.simplemobiletools.dialer.activities.CallActivity
import com.simplemobiletools.dialer.receivers.CallActionReceiver
class CallNotificationManager(private val context: Context) {
private val CALL_NOTIFICATION_ID = 1
private val ACCEPT_CALL_CODE = 0
private val DECLINE_CALL_CODE = 1
private val notificationManager = context.notificationManager
private val callContactAvatarHelper = CallContactAvatarHelper(context)
@SuppressLint("NewApi")
fun setupNotification() {
CallManager.getCallContact(context.applicationContext) { callContact ->
val callContactAvatar = callContactAvatarHelper.getCallContactAvatar(callContact)
val callState = CallManager.getState()
val channelId = "simple_dialer_call"
if (isOreoPlus()) {
val importance = NotificationManager.IMPORTANCE_DEFAULT
val name = "call_notification_channel"
NotificationChannel(channelId, name, importance).apply {
setSound(null, null)
notificationManager.createNotificationChannel(this)
}
}
val openAppIntent = CallActivity.getStartIntent(context)
val openAppPendingIntent = PendingIntent.getActivity(context, 0, openAppIntent, 0)
val acceptCallIntent = Intent(context, CallActionReceiver::class.java)
acceptCallIntent.action = ACCEPT_CALL
val acceptPendingIntent = PendingIntent.getBroadcast(context, ACCEPT_CALL_CODE, acceptCallIntent, PendingIntent.FLAG_CANCEL_CURRENT)
val declineCallIntent = Intent(context, CallActionReceiver::class.java)
declineCallIntent.action = DECLINE_CALL
val declinePendingIntent = PendingIntent.getBroadcast(context, DECLINE_CALL_CODE, declineCallIntent, PendingIntent.FLAG_CANCEL_CURRENT)
val callerName = if (callContact != null && callContact.name.isNotEmpty()) callContact.name else context.getString(R.string.unknown_caller)
val contentTextId = when (callState) {
Call.STATE_RINGING -> R.string.is_calling
Call.STATE_DIALING -> R.string.dialing
Call.STATE_DISCONNECTED -> R.string.call_ended
Call.STATE_DISCONNECTING -> R.string.call_ending
else -> R.string.ongoing_call
}
val collapsedView = RemoteViews(context.packageName, R.layout.call_notification).apply {
setText(R.id.notification_caller_name, callerName)
setText(R.id.notification_call_status, context.getString(contentTextId))
setVisibleIf(R.id.notification_accept_call, callState == Call.STATE_RINGING)
setOnClickPendingIntent(R.id.notification_decline_call, declinePendingIntent)
setOnClickPendingIntent(R.id.notification_accept_call, acceptPendingIntent)
if (callContactAvatar != null) {
setImageViewBitmap(R.id.notification_thumbnail, callContactAvatarHelper.getCircularBitmap(callContactAvatar))
}
}
val builder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_phone_vector)
.setContentIntent(openAppPendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(Notification.CATEGORY_CALL)
.setCustomContentView(collapsedView)
.setOngoing(true)
.setSound(null)
.setUsesChronometer(callState == Call.STATE_ACTIVE)
.setChannelId(channelId)
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
val notification = builder.build()
notificationManager.notify(CALL_NOTIFICATION_ID, notification)
}
}
fun cancelNotification() {
notificationManager.cancel(CALL_NOTIFICATION_ID)
}
}

View File

@ -3,6 +3,7 @@ package com.simplemobiletools.dialer.receivers
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.simplemobiletools.dialer.activities.CallActivity
import com.simplemobiletools.dialer.helpers.ACCEPT_CALL import com.simplemobiletools.dialer.helpers.ACCEPT_CALL
import com.simplemobiletools.dialer.helpers.CallManager import com.simplemobiletools.dialer.helpers.CallManager
import com.simplemobiletools.dialer.helpers.DECLINE_CALL import com.simplemobiletools.dialer.helpers.DECLINE_CALL
@ -10,7 +11,10 @@ import com.simplemobiletools.dialer.helpers.DECLINE_CALL
class CallActionReceiver : BroadcastReceiver() { class CallActionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
when (intent.action) { when (intent.action) {
ACCEPT_CALL -> CallManager.accept() ACCEPT_CALL -> {
context.startActivity(CallActivity.getStartIntent(context))
CallManager.accept()
}
DECLINE_CALL -> CallManager.reject() DECLINE_CALL -> CallManager.reject()
} }
} }

View File

@ -1,24 +1,51 @@
package com.simplemobiletools.dialer.services package com.simplemobiletools.dialer.services
import android.content.Intent
import android.telecom.Call import android.telecom.Call
import android.telecom.InCallService import android.telecom.InCallService
import com.simplemobiletools.dialer.App
import com.simplemobiletools.dialer.activities.CallActivity import com.simplemobiletools.dialer.activities.CallActivity
import com.simplemobiletools.dialer.helpers.CallManager import com.simplemobiletools.dialer.helpers.CallManager
import com.simplemobiletools.dialer.helpers.CallNotificationManager
class CallService : InCallService() { class CallService : InCallService() {
private val callNotificationManager by lazy { CallNotificationManager(this) }
private val callDurationHelper by lazy { (application as App).callDurationHelper }
private val callListener = object : Call.Callback() {
override fun onStateChanged(call: Call, state: Int) {
super.onStateChanged(call, state)
if (state != Call.STATE_DISCONNECTED) {
callNotificationManager.setupNotification()
}
if (state == Call.STATE_ACTIVE) {
callDurationHelper.start()
} else if (state == Call.STATE_DISCONNECTED || state == Call.STATE_DISCONNECTING) {
callDurationHelper.cancel()
}
}
}
override fun onCallAdded(call: Call) { override fun onCallAdded(call: Call) {
super.onCallAdded(call) super.onCallAdded(call)
val intent = Intent(this, CallActivity::class.java) startActivity(CallActivity.getStartIntent(this))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
CallManager.call = call CallManager.call = call
CallManager.inCallService = this CallManager.inCallService = this
CallManager.registerCallback(callListener)
callNotificationManager.setupNotification()
} }
override fun onCallRemoved(call: Call) { override fun onCallRemoved(call: Call) {
super.onCallRemoved(call) super.onCallRemoved(call)
CallManager.call = null CallManager.call = null
CallManager.inCallService = null CallManager.inCallService = null
callNotificationManager.cancelNotification()
}
override fun onDestroy() {
super.onDestroy()
CallManager.registerCallback(callListener)
callNotificationManager.cancelNotification()
callDurationHelper.cancel()
} }
} }