diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 63dd43791..0275d9b63 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -168,6 +168,16 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/kotlin/org/fossify/phone/activities/MissedCallNotificationActivity.kt b/app/src/main/kotlin/org/fossify/phone/activities/MissedCallNotificationActivity.kt new file mode 100644 index 000000000..43029b27f --- /dev/null +++ b/app/src/main/kotlin/org/fossify/phone/activities/MissedCallNotificationActivity.kt @@ -0,0 +1,48 @@ +package org.fossify.phone.activities + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import org.fossify.phone.helpers.MISSED_CALL_BACK +import org.fossify.phone.helpers.MISSED_CALL_CANCEL +import org.fossify.phone.helpers.MISSED_CALL_MESSAGE +import org.fossify.phone.receivers.MissedCallReceiver + +class MissedCallNotificationActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val phoneNumber = intent.extras?.getString("phoneNumber") ?: return + val notificationId = intent.extras?.getInt("notificationId", -1) ?: return + + when (intent.action) { + MISSED_CALL_BACK -> phoneNumber.let { + Intent(Intent.ACTION_CALL).apply { + data = Uri.fromParts("tel", it, null) + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + } + + MISSED_CALL_MESSAGE -> phoneNumber.let { + Intent(Intent.ACTION_VIEW).apply { + data = Uri.fromParts("sms", it, null) + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + } + + else -> null + }?.let { + startActivity(it) + sendBroadcast( + Intent(this, MissedCallReceiver::class.java).apply { + action = MISSED_CALL_CANCEL + putExtra("notificationId", notificationId) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + ) + } + + finish() + } +} diff --git a/app/src/main/kotlin/org/fossify/phone/helpers/Constants.kt b/app/src/main/kotlin/org/fossify/phone/helpers/Constants.kt index a9c144d07..db681135c 100644 --- a/app/src/main/kotlin/org/fossify/phone/helpers/Constants.kt +++ b/app/src/main/kotlin/org/fossify/phone/helpers/Constants.kt @@ -27,5 +27,9 @@ val tabsList = arrayListOf(TAB_CONTACTS, TAB_FAVORITES, TAB_CALL_HISTORY) private const val PATH = "org.fossify.phone.action." const val ACCEPT_CALL = PATH + "accept_call" const val DECLINE_CALL = PATH + "decline_call" +const val MISSED_CALLS = PATH + "missed_call" +const val MISSED_CALL_BACK = PATH + "missed_call_back" +const val MISSED_CALL_MESSAGE = PATH + "missed_call_message" +const val MISSED_CALL_CANCEL = PATH + "missed_call_cancel" const val DIALPAD_TONE_LENGTH_MS = 150L // The length of DTMF tones in milliseconds diff --git a/app/src/main/kotlin/org/fossify/phone/receivers/MissedCallReceiver.kt b/app/src/main/kotlin/org/fossify/phone/receivers/MissedCallReceiver.kt new file mode 100644 index 000000000..b25a91a25 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/phone/receivers/MissedCallReceiver.kt @@ -0,0 +1,161 @@ +package org.fossify.phone.receivers + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.graphics.drawable.Icon +import android.os.Build +import android.telecom.TelecomManager +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import org.fossify.commons.extensions.* +import org.fossify.commons.helpers.ContactsHelper +import org.fossify.commons.helpers.MyContactsContentProvider +import org.fossify.commons.models.PhoneNumber +import org.fossify.phone.R +import org.fossify.phone.activities.MissedCallNotificationActivity +import org.fossify.phone.helpers.MISSED_CALLS +import org.fossify.phone.helpers.MISSED_CALL_BACK +import org.fossify.phone.helpers.MISSED_CALL_CANCEL +import org.fossify.phone.helpers.MISSED_CALL_MESSAGE +import kotlin.random.Random + +@RequiresApi(Build.VERSION_CODES.O) +class MissedCallReceiver : BroadcastReceiver() { + companion object { + private var notifications = 0 + } + + override fun onReceive(context: Context, intent: Intent) { + val extras = intent.extras ?: return + val notificationManager = context.notificationManager + + when (intent.action) { + TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION -> { + val notificationCount = extras.getInt(TelecomManager.EXTRA_NOTIFICATION_COUNT) + if (notificationCount != 0) { + val notificationId = Random.nextInt() + val phoneNumber = extras.getString(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER) + createNotificationChannel(context) + notifications++ + notificationManager.notify(MISSED_CALLS.hashCode(), getNotificationGroup(context)) + notifyMissedCall(context, notificationId, phoneNumber ?: return) + } + } + + MISSED_CALL_CANCEL -> { + val notificationId = intent.extras?.getInt("notificationId", -1) ?: return + notificationManager.cancel(notificationId) + notifications-- + if (notifications <= 0) { + notificationManager.cancel(MISSED_CALLS.hashCode()) + context.telecomManager.cancelMissedCallsNotification() + } + } + } + } + + private fun createNotificationChannel(context: Context) { + val notificationManager = context.notificationManager + val channel = NotificationChannel( + "missed_call_channel", + context.getString(R.string.missed_call_channel), + NotificationManager.IMPORTANCE_DEFAULT + ) + notificationManager.createNotificationChannel(channel) + } + + private fun launchIntent(context: Context): PendingIntent { + return PendingIntent.getActivity( + context, 0, context.getLaunchIntent(), PendingIntent.FLAG_IMMUTABLE + ) + } + + private fun getNotificationGroup(context: Context): Notification { + return NotificationCompat.Builder(context, "missed_call_channel") + .setSmallIcon(android.R.drawable.sym_call_missed) + .setAutoCancel(true) + .setGroupSummary(true) + .setGroup(MISSED_CALLS) + .setContentIntent(launchIntent(context)) + .build() + } + + private fun notifyMissedCall(context: Context, notificationId: Int, phoneNumber: String) { + val privateCursor = context.getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true) + ContactsHelper(context).getContacts(getAll = true, showOnlyContactsWithNumbers = true) { contactList -> + val privateContacts = MyContactsContentProvider.getContacts(context, privateCursor) + contactList.addAll(privateContacts) + contactList.sort() + var phone: PhoneNumber? = null + val contact = contactList.firstOrNull { + it.phoneNumbers.any { + if (it.value.normalizePhoneNumber() == phoneNumber.normalizePhoneNumber()) { + phone = it + return@any true + } + false + } + } + + val name = contact?.name ?: phoneNumber + val photoUri = contact?.photoUri + var numberLabel = if (contact != null && phone != null && contact.phoneNumbers.size > 1) { + context.getPhoneNumberTypeText(phone!!.type, phone!!.label) + } else "" + if (numberLabel.isNotEmpty()) { + numberLabel = " - $numberLabel" + } + + val callBack = Intent(context, MissedCallNotificationActivity::class.java).apply { + action = MISSED_CALL_BACK + putExtra("notificationId", notificationId) + putExtra("phoneNumber", phoneNumber) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + val callBackIntent = PendingIntent.getActivity( + context, notificationId, callBack, PendingIntent.FLAG_IMMUTABLE + ) + + val smsIntent = Intent(context, MissedCallNotificationActivity::class.java).apply { + action = MISSED_CALL_MESSAGE + putExtra("notificationId", notificationId) + putExtra("phoneNumber", phoneNumber) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + val messageIntent = PendingIntent.getActivity( + context, notificationId, smsIntent, PendingIntent.FLAG_IMMUTABLE + ) + + val cancelIntent = Intent(context, MissedCallReceiver::class.java).apply { + action = MISSED_CALL_CANCEL + putExtra("notificationId", notificationId) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + val cancelPendingIntent = PendingIntent.getBroadcast( + context, notificationId, cancelIntent, PendingIntent.FLAG_IMMUTABLE + ) + + val notification = NotificationCompat.Builder(context, "missed_call_channel") + .setSmallIcon(android.R.drawable.sym_call_missed) + .setContentTitle(context.resources.getString(R.string.missed_calls)) + .setContentText(context.getString(R.string.missed_call_from, name) + numberLabel) + .setAutoCancel(true) + .setGroup(MISSED_CALLS) + .setContentIntent(launchIntent(context)) + .addAction(android.R.drawable.sym_action_call, context.getString(R.string.call_back), callBackIntent) + .addAction(android.R.drawable.sym_action_chat, context.getString(R.string.message), messageIntent) + .setDeleteIntent(cancelPendingIntent) + + if (!photoUri.isNullOrBlank()) { + notification.setLargeIcon(Icon.createWithContentUri(photoUri)) + } + + context.notificationManager.notify(notificationId, notification.build()) + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 858d25032..5f9277bc6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -66,6 +66,13 @@ The number you are calling is blocked Your call is being connected… + + Missed calls + Missed call + Missed call from %s + Call back + Message + Speed dial Manage speed dial