Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement missed call notifications #253

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
20 changes: 20 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@
</intent-filter>
</activity>

<activity
android:name=".activities.MissedCallNotificationActivity"
android:excludeFromRecents="true"
android:exported="true">
<intent-filter>
<action android:name="org.fossify.phone.action.MISSED_CALL_BACK" />
<action android:name="org.fossify.phone.action.MISSED_CALL_MESSAGE" />
</intent-filter>
</activity>

<service
android:name=".services.CallService"
android:enabled="true"
Expand Down Expand Up @@ -202,6 +212,16 @@
<intent-filter>
<action android:name="org.fossify.phone.action.ACCEPT_CALL" />
<action android:name="org.fossify.phone.action.DECLINE_CALL" />
<action android:name="org.fossify.phone.action.MISSED_CALL_CANCEL" />
</intent-filter>
</receiver>

<receiver
android:name=".receivers.MissedCallReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
</intent-filter>
</receiver>

Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
4 changes: 4 additions & 0 deletions app/src/main/kotlin/org/fossify/phone/helpers/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
161 changes: 161 additions & 0 deletions app/src/main/kotlin/org/fossify/phone/receivers/MissedCallReceiver.kt
Original file line number Diff line number Diff line change
@@ -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())
}
}
}
7 changes: 7 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@
<string name="choose_audio_route">Choose audio route</string>
<string name="calling_blocked_number">The number you are calling is blocked</string>

<!-- Notifications -->
<string name="missed_call_channel">Missed calls</string>
<string name="missed_calls">Missed call</string>
<string name="missed_call_from">Missed call from %s</string>
<string name="call_back">Call back</string>
<string name="message">Message</string>

<!-- Speed dial -->
<string name="speed_dial">Speed dial</string>
<string name="manage_speed_dial">Manage speed dial</string>
Expand Down
Loading