diff --git a/art/ic_missed_call_notification.svg b/art/ic_missed_call_notification.svg
new file mode 100644
index 0000000000000000000000000000000000000000..78f0acead8673ea1623d4e72e64a72b0bf012142
--- /dev/null
+++ b/art/ic_missed_call_notification.svg
@@ -0,0 +1,344 @@
+
+
+
+
diff --git a/art/render.rb b/art/render.rb
index 7fb46d138ca0bc7dac38262b8c11681e94cb7963..7ae4ac8ae18d8d6c3e8cb2c6a49ed7c661a476f7 100755
--- a/art/render.rb
+++ b/art/render.rb
@@ -28,6 +28,7 @@ images = {
'conversations_mono.svg' => ['conversations/ic_notification', 24],
'quicksy_mono.svg' => ['quicksy/ic_notification', 24],
'flip_camera_android-black-24dp.svg' => ['ic_flip_camera_android_black_24dp', 24],
+ 'ic_missed_call_notification.svg' => ['ic_missed_call_notification', 24],
'ic_send_text_offline.svg' => ['ic_send_text_offline', 36],
'ic_send_text_offline_white.svg' => ['ic_send_text_offline_white', 36],
'ic_send_text_online.svg' => ['ic_send_text_online', 36],
@@ -119,7 +120,7 @@ images.each do |source_filename, settings|
else
path = "../src/#{output_parts[0]}/res/drawable-#{resolution}/#{output_parts[1]}.png"
end
- execute_cmd "#{inkscape} #{source_filename} -C -w #{width} -h #{height} -o #{path}"
+ execute_cmd "#{inkscape} #{source_filename} -C -w #{width} -h #{height} -e #{path}"
top = []
right = []
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index aeba1f14c7f4418d6123643f14f9e986435c3d6b..0afe71f837c1f279a6c4514f8319230dd327cb18 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -241,11 +241,11 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
}
- public void findUnreadMessages(OnMessageFound onMessageFound) {
+ public void findUnreadMessagesAndCalls(OnMessageFound onMessageFound) {
final ArrayList results = new ArrayList<>();
synchronized (this.messages) {
for (final Message message : this.messages) {
- if (message.isRead() || message.getType() == Message.TYPE_RTP_SESSION) {
+ if (message.isRead()) {
continue;
}
results.add(message);
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index ca4499300ac6df8d7772211b74d8ef6649ea297d..8ce8325fa4ae6684f5dc1a72fe4af2db8da35cc5 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -87,7 +87,8 @@ public class NotificationService {
private static final long[] CALL_PATTERN = {0, 500, 300, 600};
- private static final String CONVERSATIONS_GROUP = "eu.siacs.conversations";
+ private static final String MESSAGES_GROUP = "eu.siacs.conversations.messages";
+ private static final String MISSED_CALLS_GROUP = "eu.siacs.conversations.missed_calls";
private static final int NOTIFICATION_ID_MULTIPLIER = 1024 * 1024;
static final int FOREGROUND_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 4;
private static final int NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 2;
@@ -95,9 +96,11 @@ public class NotificationService {
private static final int INCOMING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 8;
public static final int ONGOING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 10;
private static final int DELIVERY_FAILED_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 12;
+ public static final int MISSED_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 14;
private final XmppConnectionService mXmppConnectionService;
private final LinkedHashMap> notifications = new LinkedHashMap<>();
private final HashMap mBacklogMessageCounter = new HashMap<>();
+ private final LinkedHashMap mMissedCalls = new LinkedHashMap<>();
private Conversation mOpenConversation;
private boolean mIsInForeground;
private long mLastNotification;
@@ -196,6 +199,15 @@ public class NotificationService {
ongoingCallsChannel.setGroup("calls");
notificationManager.createNotificationChannel(ongoingCallsChannel);
+ final NotificationChannel missedCallsChannel = new NotificationChannel("missed_calls",
+ c.getString(R.string.missed_calls_channel_name),
+ NotificationManager.IMPORTANCE_HIGH);
+ missedCallsChannel.setShowBadge(true);
+ missedCallsChannel.setSound(null, null);
+ missedCallsChannel.setLightColor(LED_COLOR);
+ missedCallsChannel.enableLights(true);
+ missedCallsChannel.setGroup("calls");
+ notificationManager.createNotificationChannel(missedCallsChannel);
final NotificationChannel messagesChannel = new NotificationChannel("messages",
c.getString(R.string.messages_channel_name),
@@ -247,12 +259,18 @@ public class NotificationService {
notificationManager.createNotificationChannel(deliveryFailedChannel);
}
- private boolean notify(final Message message) {
+ private boolean notifyMessage(final Message message) {
final Conversation conversation = (Conversation) message.getConversation();
return message.getStatus() == Message.STATUS_RECEIVED
&& !conversation.isMuted()
&& (conversation.alwaysNotify() || wasHighlightedOrPrivate(message))
- && (!conversation.isWithStranger() || notificationsFromStrangers());
+ && (!conversation.isWithStranger() || notificationsFromStrangers())
+ && message.getType() != Message.TYPE_RTP_SESSION;
+ }
+
+ private boolean notifyMissedCall(final Message message) {
+ return message.getType() == Message.TYPE_RTP_SESSION
+ && message.getStatus() == Message.STATUS_RECEIVED;
}
public boolean notificationsFromStrangers() {
@@ -276,11 +294,15 @@ public class NotificationService {
}
public void pushFromBacklog(final Message message) {
- if (notify(message)) {
+ if (notifyMessage(message)) {
synchronized (notifications) {
getBacklogMessageCounter((Conversation) message.getConversation()).incrementAndGet();
pushToStack(message);
}
+ } else if (notifyMissedCall(message)) {
+ synchronized (mMissedCalls) {
+ pushMissedCall(message);
+ }
}
}
@@ -315,6 +337,9 @@ public class NotificationService {
updateNotification(count > 0, conversations);
}
}
+ synchronized (mMissedCalls) {
+ updateMissedCallNotifications(mMissedCalls.keySet());
+ }
}
private List getBacklogConversations(Account account) {
@@ -562,7 +587,7 @@ public class NotificationService {
private void pushNow(final Message message) {
mXmppConnectionService.updateUnreadCountBadge();
- if (!notify(message)) {
+ if (!notifyMessage(message)) {
Log.d(Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() + ": suppressing notification because turned off");
return;
}
@@ -582,7 +607,29 @@ public class NotificationService {
}
}
- public void clear() {
+ private void pushMissedCall(final Message message) {
+ final Conversational conversation = message.getConversation();
+ final MissedCallsInfo info = mMissedCalls.get(conversation);
+ if (info == null) {
+ mMissedCalls.put(conversation, new MissedCallsInfo(message.getTimeSent()));
+ } else {
+ info.newMissedCall(message.getTimeSent());
+ }
+ }
+
+ public void pushMissedCallNow(final Message message) {
+ synchronized (mMissedCalls) {
+ pushMissedCall(message);
+ updateMissedCallNotifications(Collections.singleton(message.getConversation()));
+ }
+ }
+
+ public void clear(final Conversation conversation) {
+ clearMessages(conversation);
+ clearMissedCalls(conversation);
+ }
+
+ public void clearMessages() {
synchronized (notifications) {
for (ArrayList messages : notifications.values()) {
markAsReadIfHasDirectReply(messages);
@@ -592,7 +639,7 @@ public class NotificationService {
}
}
- public void clear(final Conversation conversation) {
+ public void clearMessages(final Conversation conversation) {
synchronized (this.mBacklogMessageCounter) {
this.mBacklogMessageCounter.remove(conversation);
}
@@ -605,6 +652,25 @@ public class NotificationService {
}
}
+ public void clearMissedCalls() {
+ synchronized (mMissedCalls) {
+ for (final Conversational conversation : mMissedCalls.keySet()) {
+ cancel(conversation.getUuid(), MISSED_CALL_NOTIFICATION_ID);
+ }
+ mMissedCalls.clear();
+ updateMissedCallNotifications(null);
+ }
+ }
+
+ public void clearMissedCalls(final Conversation conversation) {
+ synchronized (mMissedCalls) {
+ if (mMissedCalls.remove(conversation) != null) {
+ cancel(conversation.getUuid(), MISSED_CALL_NOTIFICATION_ID);
+ updateMissedCallNotifications(null);
+ }
+ }
+ }
+
private void markAsReadIfHasDirectReply(final Conversation conversation) {
markAsReadIfHasDirectReply(notifications.get(conversation.getUuid()));
}
@@ -672,7 +738,7 @@ public class NotificationService {
singleBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
}
modifyForSoundVibrationAndLight(singleBuilder, notifyThis, quiteHours, preferences);
- singleBuilder.setGroup(CONVERSATIONS_GROUP);
+ singleBuilder.setGroup(MESSAGES_GROUP);
setNotificationColor(singleBuilder);
notify(entry.getKey(), NOTIFICATION_ID, singleBuilder.build());
}
@@ -682,6 +748,31 @@ public class NotificationService {
}
}
+ private void updateMissedCallNotifications(final Set update) {
+ if (mMissedCalls.isEmpty()) {
+ cancel(MISSED_CALL_NOTIFICATION_ID);
+ return;
+ }
+ if (mMissedCalls.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ final Conversational conversation = mMissedCalls.keySet().iterator().next();
+ final MissedCallsInfo info = mMissedCalls.values().iterator().next();
+ final Notification notification = missedCall(conversation, info);
+ notify(MISSED_CALL_NOTIFICATION_ID, notification);
+ } else {
+ final Notification summary = missedCallsSummary();
+ notify(MISSED_CALL_NOTIFICATION_ID, summary);
+ if (update != null) {
+ for (final Conversational conversation : update) {
+ final MissedCallsInfo info = mMissedCalls.get(conversation);
+ if (info != null) {
+ final Notification notification = missedCall(conversation, info);
+ notify(conversation.getUuid(), MISSED_CALL_NOTIFICATION_ID, notification);
+ }
+ }
+ }
+ }
+ }
+
private void modifyForSoundVibrationAndLight(Builder mBuilder, boolean notify, boolean quietHours, SharedPreferences preferences) {
final Resources resources = mXmppConnectionService.getResources();
final String ringtone = preferences.getString("notification_ringtone", resources.getString(R.string.notification_ringtone));
@@ -730,6 +821,101 @@ public class NotificationService {
}
}
+ private Notification missedCallsSummary() {
+ final Builder publicBuilder = buildMissedCallsSummary(true);
+ final Builder builder = buildMissedCallsSummary(false);
+ builder.setPublicVersion(publicBuilder.build());
+ return builder.build();
+ }
+
+ private Builder buildMissedCallsSummary(boolean publicVersion) {
+ final Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "missed_calls");
+ int totalCalls = 0;
+ final StringBuilder names = new StringBuilder();
+ long lastTime = 0;
+ for (Map.Entry entry : mMissedCalls.entrySet()) {
+ final Conversational conversation = entry.getKey();
+ final MissedCallsInfo missedCallsInfo = entry.getValue();
+ names.append(conversation.getContact().getDisplayName());
+ names.append(", ");
+ totalCalls += missedCallsInfo.getNumberOfCalls();
+ lastTime = Math.max(lastTime, missedCallsInfo.getLastTime());
+ }
+ if (names.length() >= 2) {
+ names.delete(names.length() - 2, names.length());
+ }
+ final String title = (totalCalls == 1) ? mXmppConnectionService.getString(R.string.missed_call) :
+ (mMissedCalls.size() == 1) ? mXmppConnectionService.getString(R.string.n_missed_calls, totalCalls) :
+ mXmppConnectionService.getString(R.string.n_missed_calls_from_m_contacts, totalCalls, mMissedCalls.size());
+ builder.setContentTitle(title);
+ builder.setTicker(title);
+ if (!publicVersion) {
+ builder.setContentText(names.toString());
+ }
+ builder.setSmallIcon(R.drawable.ic_missed_call_notification);
+ builder.setGroupSummary(true);
+ builder.setGroup(MISSED_CALLS_GROUP);
+ builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
+ builder.setCategory(NotificationCompat.CATEGORY_CALL);
+ builder.setWhen(lastTime);
+ if (!mMissedCalls.isEmpty()) {
+ final Conversational firstConversation = mMissedCalls.keySet().iterator().next();
+ builder.setContentIntent(createContentIntent(firstConversation));
+ }
+ builder.setDeleteIntent(createMissedCallsDeleteIntent(null));
+ modifyMissedCall(builder);
+ return builder;
+ }
+
+ private Notification missedCall(final Conversational conversation, final MissedCallsInfo info) {
+ final Builder publicBuilder = buildMissedCall(conversation, info, true);
+ final Builder builder = buildMissedCall(conversation, info, false);
+ builder.setPublicVersion(publicBuilder.build());
+ return builder.build();
+ }
+
+ private Builder buildMissedCall(final Conversational conversation, final MissedCallsInfo info, boolean publicVersion) {
+ final Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "missed_calls");
+ final String title = (info.getNumberOfCalls() == 1) ? mXmppConnectionService.getString(R.string.missed_call) :
+ mXmppConnectionService.getString(R.string.n_missed_calls, info.getNumberOfCalls());
+ builder.setContentTitle(title);
+ final String name = conversation.getContact().getDisplayName();
+ if (publicVersion) {
+ builder.setTicker(title);
+ } else {
+ if (info.getNumberOfCalls() == 1) {
+ builder.setTicker(mXmppConnectionService.getString(R.string.missed_call_from_x, name));
+ } else {
+ builder.setTicker(mXmppConnectionService.getString(R.string.n_missed_calls_from_x, info.getNumberOfCalls(), name));
+ }
+ builder.setContentText(name);
+ }
+ builder.setSmallIcon(R.drawable.ic_missed_call_notification);
+ builder.setGroup(MISSED_CALLS_GROUP);
+ builder.setCategory(NotificationCompat.CATEGORY_CALL);
+ builder.setWhen(info.getLastTime());
+ builder.setContentIntent(createContentIntent(conversation));
+ builder.setDeleteIntent(createMissedCallsDeleteIntent(conversation));
+ if (!publicVersion && conversation instanceof Conversation) {
+ builder.setLargeIcon(mXmppConnectionService.getAvatarService()
+ .get((Conversation) conversation, AvatarService.getSystemUiAvatarSize(mXmppConnectionService)));
+ }
+ modifyMissedCall(builder);
+ return builder;
+ }
+
+ private void modifyMissedCall(final Builder builder) {
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
+ final Resources resources = mXmppConnectionService.getResources();
+ final boolean led = preferences.getBoolean("led", resources.getBoolean(R.bool.led));
+ if (led) {
+ builder.setLights(LED_COLOR, 2000, 3000);
+ }
+ builder.setPriority(NotificationCompat.PRIORITY_HIGH);
+ builder.setSound(null);
+ setNotificationColor(builder);
+ }
+
private Builder buildMultipleConversation(final boolean notify, final boolean quietHours) {
final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
@@ -767,7 +953,7 @@ public class NotificationService {
mBuilder.setContentIntent(createContentIntent(conversation));
}
mBuilder.setGroupSummary(true);
- mBuilder.setGroup(CONVERSATIONS_GROUP);
+ mBuilder.setGroup(MESSAGES_GROUP);
mBuilder.setDeleteIntent(createDeleteIntent(null));
mBuilder.setSmallIcon(R.drawable.ic_notification);
return mBuilder;
@@ -1069,7 +1255,7 @@ public class NotificationService {
private PendingIntent createDeleteIntent(Conversation conversation) {
final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
- intent.setAction(XmppConnectionService.ACTION_CLEAR_NOTIFICATION);
+ intent.setAction(XmppConnectionService.ACTION_CLEAR_MESSAGE_NOTIFICATION);
if (conversation != null) {
intent.putExtra("uuid", conversation.getUuid());
return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 20), intent, 0);
@@ -1077,6 +1263,16 @@ public class NotificationService {
return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
}
+ private PendingIntent createMissedCallsDeleteIntent(final Conversational conversation) {
+ final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
+ intent.setAction(XmppConnectionService.ACTION_CLEAR_MISSED_CALL_NOTIFICATION);
+ if (conversation != null) {
+ intent.putExtra("uuid", conversation.getUuid());
+ return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 21), intent, 0);
+ }
+ return PendingIntent.getService(mXmppConnectionService, 1, intent, 0);
+ }
+
private PendingIntent createReplyIntent(final Conversation conversation, final String lastMessageUuid, final boolean dismissAfterReply) {
final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION);
@@ -1332,6 +1528,29 @@ public class NotificationService {
public void run() {
final Vibrator vibrator = (Vibrator) mXmppConnectionService.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(CALL_PATTERN, -1);
+ }
+ }
+
+ private static class MissedCallsInfo {
+ private int numberOfCalls;
+ private long lastTime;
+
+ MissedCallsInfo(final long time) {
+ numberOfCalls = 1;
+ lastTime = time;
+ }
+
+ public void newMissedCall(final long time) {
+ ++numberOfCalls;
+ lastTime = time;
+ }
+
+ public int getNumberOfCalls() {
+ return numberOfCalls;
+ }
+
+ public long getLastTime() {
+ return lastTime;
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index e0f1c2feea00f6e69d6f932327c0c91f1cd1f3d8..a1f855158f5a864b375bf1488b6f898fb82b41ed 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -172,7 +172,8 @@ public class XmppConnectionService extends Service {
public static final String ACTION_REPLY_TO_CONVERSATION = "reply_to_conversations";
public static final String ACTION_MARK_AS_READ = "mark_as_read";
public static final String ACTION_SNOOZE = "snooze";
- public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification";
+ public static final String ACTION_CLEAR_MESSAGE_NOTIFICATION = "clear_message_notification";
+ public static final String ACTION_CLEAR_MISSED_CALL_NOTIFICATION = "clear_missed_call_notification";
public static final String ACTION_DISMISS_ERROR_NOTIFICATIONS = "dismiss_error";
public static final String ACTION_TRY_AGAIN = "try_again";
public static final String ACTION_IDLE_PING = "idle_ping";
@@ -677,19 +678,35 @@ public class XmppConnectionService extends Service {
case Intent.ACTION_SHUTDOWN:
logoutAndSave(true);
return START_NOT_STICKY;
- case ACTION_CLEAR_NOTIFICATION:
+ case ACTION_CLEAR_MESSAGE_NOTIFICATION:
mNotificationExecutor.execute(() -> {
try {
final Conversation c = findConversationByUuid(uuid);
if (c != null) {
- mNotificationService.clear(c);
+ mNotificationService.clearMessages(c);
} else {
- mNotificationService.clear();
+ mNotificationService.clearMessages();
}
restoredFromDatabaseLatch.await();
} catch (InterruptedException e) {
- Log.d(Config.LOGTAG, "unable to process clear notification");
+ Log.d(Config.LOGTAG, "unable to process clear message notification");
+ }
+ });
+ break;
+ case ACTION_CLEAR_MISSED_CALL_NOTIFICATION:
+ mNotificationExecutor.execute(() -> {
+ try {
+ final Conversation c = findConversationByUuid(uuid);
+ if (c != null) {
+ mNotificationService.clearMissedCalls(c);
+ } else {
+ mNotificationService.clearMissedCalls();
+ }
+ restoredFromDatabaseLatch.await();
+
+ } catch (InterruptedException e) {
+ Log.d(Config.LOGTAG, "unable to process clear missed call notification");
}
});
break;
@@ -776,7 +793,7 @@ public class XmppConnectionService extends Service {
return;
}
c.setMutedTill(System.currentTimeMillis() + 30 * 60 * 1000);
- mNotificationService.clear(c);
+ mNotificationService.clearMessages(c);
updateConversation(c);
});
case AudioManager.RINGER_MODE_CHANGED_ACTION:
@@ -1945,7 +1962,7 @@ public class XmppConnectionService extends Service {
private void restoreMessages(Conversation conversation) {
conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
conversation.findUnsentTextMessages(message -> markMessage(message, Message.STATUS_WAITING));
- conversation.findUnreadMessages(mNotificationService::pushFromBacklog);
+ conversation.findUnreadMessagesAndCalls(mNotificationService::pushFromBacklog);
}
public void loadPhoneContacts() {
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java
index 46d89ec3f84e073f9704549ef8aeab915f6ef8fb..4d97ed23fd95e1a7f11653cb9cb3240f1d272c47 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java
@@ -865,6 +865,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
rejectCallFromSessionInitiate();
break;
}
+ xmppConnectionService.getNotificationService().pushMissedCallNow(message);
}
private void cancelRingingTimeout() {
@@ -916,6 +917,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
final State target = this.state == State.PROCEED ? State.RETRACTED_RACED : State.RETRACTED;
if (transition(target)) {
xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
+ xmppConnectionService.getNotificationService().pushMissedCallNow(message);
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": session with " + id.with + " has been retracted (serverMsgId=" + serverMsgId + ")");
if (serverMsgId != null) {
this.message.setServerMsgId(serverMsgId);
diff --git a/src/main/res/drawable-hdpi/ic_missed_call_notification.png b/src/main/res/drawable-hdpi/ic_missed_call_notification.png
new file mode 100644
index 0000000000000000000000000000000000000000..3608ebd92478ee32c52ad4feba3b261218ebc50d
Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_missed_call_notification.png differ
diff --git a/src/main/res/drawable-mdpi/ic_missed_call_notification.png b/src/main/res/drawable-mdpi/ic_missed_call_notification.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c6c37da0bc5fee31d765aadb8c7f61412bc83d1
Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_missed_call_notification.png differ
diff --git a/src/main/res/drawable-xhdpi/ic_missed_call_notification.png b/src/main/res/drawable-xhdpi/ic_missed_call_notification.png
new file mode 100644
index 0000000000000000000000000000000000000000..80cd15819fcca113e91cd6a57468a3062926bfed
Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_missed_call_notification.png differ
diff --git a/src/main/res/drawable-xxhdpi/ic_missed_call_notification.png b/src/main/res/drawable-xxhdpi/ic_missed_call_notification.png
new file mode 100644
index 0000000000000000000000000000000000000000..0072d2ef0d9253358efbc88d9ed7882b77e27be1
Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_missed_call_notification.png differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_missed_call_notification.png b/src/main/res/drawable-xxxhdpi/ic_missed_call_notification.png
new file mode 100644
index 0000000000000000000000000000000000000000..b4343bb10503c1b3a4e8bb1f50806bbb619a208f
Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_missed_call_notification.png differ
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 24d6ce70fdce9f84fa771328043b725ac9e47788..a68a5d4c55a566eaa2a561606256607c0d0957d8 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -765,6 +765,7 @@
Messages
Incoming calls
Ongoing calls
+ Missed calls
Silent messages
This notification group is used to display notifications that should not trigger any sound. For example when being active on another device (Grace Period).
Failed deliveries
@@ -936,6 +937,10 @@
Outgoing call (%s)
Outgoing call (%s) . %s
Missed call
+ Missed call from %s
+ %1$d missed calls from %2$s
+ %d missed calls
+ %1$d missed calls from %2$d contacts
Audio call
Video call
Help