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
commit 47577234bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 249 additions and 155 deletions

View File

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

View File

@ -1,50 +1,50 @@
package com.simplemobiletools.dialer.activities
import android.annotation.SuppressLint
import android.app.*
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.graphics.*
import android.graphics.Bitmap
import android.media.AudioManager
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.provider.MediaStore
import android.telecom.Call
import android.telecom.CallAudioState
import android.util.Size
import android.view.WindowManager
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
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.extensions.addCharacter
import com.simplemobiletools.dialer.extensions.audioManager
import com.simplemobiletools.dialer.extensions.config
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.DECLINE_CALL
import com.simplemobiletools.dialer.models.CallContact
import com.simplemobiletools.dialer.receivers.CallActionReceiver
import kotlinx.android.synthetic.main.activity_call.*
import kotlinx.android.synthetic.main.dialpad.*
import java.util.*
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 isMicrophoneOn = true
private var isCallEnded = false
private var callDuration = 0
private var callContact: CallContact? = null
private var callContactAvatar: Bitmap? = 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?) {
supportActionBar?.hide()
@ -58,10 +58,9 @@ class CallActivity : SimpleActivity() {
CallManager.getCallContact(applicationContext) { contact ->
callContact = contact
callContactAvatar = getCallContactAvatar()
val avatar = callContactAvatarHelper.getCallContactAvatar(contact)
runOnUiThread {
setupNotification()
updateOtherPersonsInfo()
updateOtherPersonsInfo(avatar)
checkCalledSIMCard()
}
}
@ -74,14 +73,10 @@ class CallActivity : SimpleActivity() {
override fun onDestroy() {
super.onDestroy()
notificationManager.cancel(CALL_NOTIFICATION_ID)
CallManager.unregisterCallback(callCallback)
callTimer.cancel()
if (proximityWakeLock?.isHeld == true) {
proximityWakeLock!!.release()
}
endCall()
}
override fun onBackPressed() {
@ -92,7 +87,8 @@ class CallActivity : SimpleActivity() {
super.onBackPressed()
}
if (CallManager.getState() == Call.STATE_DIALING) {
val callState = CallManager.getState()
if (callState == Call.STATE_CONNECTING || callState == Call.STATE_DIALING) {
endCall()
}
}
@ -180,7 +176,7 @@ class CallActivity : SimpleActivity() {
}
}
private fun updateOtherPersonsInfo() {
private fun updateOtherPersonsInfo(avatar: Bitmap?) {
if (callContact == null) {
return
}
@ -192,8 +188,8 @@ class CallActivity : SimpleActivity() {
caller_number_label.beGone()
}
if (callContactAvatar != null) {
caller_avatar.setImageBitmap(callContactAvatar)
if (avatar != null) {
caller_avatar.setImageBitmap(avatar)
}
}
@ -223,10 +219,6 @@ class CallActivity : SimpleActivity() {
Call.STATE_SELECT_PHONE_ACCOUNT -> showPhoneAccountPicker()
}
if (state == Call.STATE_DISCONNECTED || state == Call.STATE_DISCONNECTING) {
callTimer.cancel()
}
val statusTextId = when (state) {
Call.STATE_RINGING -> R.string.is_calling
Call.STATE_DIALING -> R.string.dialing
@ -236,8 +228,6 @@ class CallActivity : SimpleActivity() {
if (statusTextId != 0) {
call_status_label.text = getString(statusTextId)
}
setupNotification()
}
private fun acceptCall() {
@ -258,9 +248,13 @@ class CallActivity : SimpleActivity() {
initProximitySensor()
incoming_call_holder.beGone()
ongoing_call_holder.beVisible()
try {
callTimer.scheduleAtFixedRate(getCallTimerUpdateTask(), 1000, 1000)
} catch (ignored: Exception) {
callDurationHelper.onDurationChange {
callDuration = it
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() {
override fun onStateChanged(call: Call, state: Int) {
super.onStateChanged(call, state)
@ -348,106 +331,4 @@ class CallActivity : SimpleActivity() {
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.Context
import android.content.Intent
import com.simplemobiletools.dialer.activities.CallActivity
import com.simplemobiletools.dialer.helpers.ACCEPT_CALL
import com.simplemobiletools.dialer.helpers.CallManager
import com.simplemobiletools.dialer.helpers.DECLINE_CALL
@ -10,7 +11,10 @@ import com.simplemobiletools.dialer.helpers.DECLINE_CALL
class CallActionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
ACCEPT_CALL -> CallManager.accept()
ACCEPT_CALL -> {
context.startActivity(CallActivity.getStartIntent(context))
CallManager.accept()
}
DECLINE_CALL -> CallManager.reject()
}
}

View File

@ -1,24 +1,51 @@
package com.simplemobiletools.dialer.services
import android.content.Intent
import android.telecom.Call
import android.telecom.InCallService
import com.simplemobiletools.dialer.App
import com.simplemobiletools.dialer.activities.CallActivity
import com.simplemobiletools.dialer.helpers.CallManager
import com.simplemobiletools.dialer.helpers.CallNotificationManager
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) {
super.onCallAdded(call)
val intent = Intent(this, CallActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
startActivity(CallActivity.getStartIntent(this))
CallManager.call = call
CallManager.inCallService = this
CallManager.registerCallback(callListener)
callNotificationManager.setupNotification()
}
override fun onCallRemoved(call: Call) {
super.onCallRemoved(call)
CallManager.call = null
CallManager.inCallService = null
callNotificationManager.cancelNotification()
}
override fun onDestroy() {
super.onDestroy()
CallManager.registerCallback(callListener)
callNotificationManager.cancelNotification()
callDurationHelper.cancel()
}
}