diff options
author | Dmitry Markin <dmitry@markin.tech> | 2022-08-29 13:41:35 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-29 13:41:35 +0300 |
commit | a6b88ba9e95deaeeeff6a4433d1e79a28d74cd25 (patch) | |
tree | dc10a12b619f51faeeecb6891da99f42c8a0e9b9 | |
parent | e2612709af160c9001c1c6000fdcd004aa7fd009 (diff) |
Add missed call notifications
Co-authored-by: Daniel Gultsch <daniel@gultsch.de>
-rw-r--r-- | art/ic_missed_call_notification.svg | 344 | ||||
-rwxr-xr-x | art/render.rb | 3 | ||||
-rw-r--r-- | src/main/java/eu/siacs/conversations/entities/Conversation.java | 4 | ||||
-rw-r--r-- | src/main/java/eu/siacs/conversations/services/NotificationService.java | 241 | ||||
-rw-r--r-- | src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 31 | ||||
-rw-r--r-- | src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java | 2 | ||||
-rw-r--r-- | src/main/res/drawable-hdpi/ic_missed_call_notification.png | bin | 0 -> 810 bytes | |||
-rw-r--r-- | src/main/res/drawable-mdpi/ic_missed_call_notification.png | bin | 0 -> 589 bytes | |||
-rw-r--r-- | src/main/res/drawable-xhdpi/ic_missed_call_notification.png | bin | 0 -> 1151 bytes | |||
-rw-r--r-- | src/main/res/drawable-xxhdpi/ic_missed_call_notification.png | bin | 0 -> 1680 bytes | |||
-rw-r--r-- | src/main/res/drawable-xxxhdpi/ic_missed_call_notification.png | bin | 0 -> 2179 bytes | |||
-rw-r--r-- | src/main/res/values/strings.xml | 5 |
12 files changed, 609 insertions, 21 deletions
diff --git a/art/ic_missed_call_notification.svg b/art/ic_missed_call_notification.svg new file mode 100644 index 000000000..78f0acead --- /dev/null +++ b/art/ic_missed_call_notification.svg @@ -0,0 +1,344 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="512" + height="512" + id="svg2" + version="1.1" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="ic_missed_call_notification.svg" + inkscape:export-filename="/home/diesys/diesys/grafica/conversation/conversation_bubble.png" + inkscape:export-xdpi="100" + inkscape:export-ydpi="100"> + <defs + id="defs4"> + <linearGradient + inkscape:collect="always" + id="linearGradient3874"> + <stop + style="stop-color:#00a000;stop-opacity:1;" + offset="0" + id="stop3876" /> + <stop + style="stop-color:#00a000;stop-opacity:0;" + offset="1" + id="stop3878" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient3913"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop3915" /> + <stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop3917" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient3818"> + <stop + style="stop-color:#669900;stop-opacity:1" + offset="0" + id="stop3820" /> + <stop + style="stop-color:#99cc00;stop-opacity:1" + offset="1" + id="stop3822" /> + </linearGradient> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3818" + id="radialGradient3824" + cx="212.07048" + cy="1045.9178" + fx="212.07048" + fy="1045.9178" + r="238.57143" + gradientTransform="matrix(1.9491621,-0.90817722,0.65829208,1.4128498,-879.63121,-248.98648)" + gradientUnits="userSpaceOnUse" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3913" + id="radialGradient3919" + cx="362.98563" + cy="379.77524" + fx="362.98563" + fy="379.77524" + r="139.95312" + gradientTransform="matrix(1.3800477,1.0445431,-1.3325077,1.7605059,339.09383,-577.83938)" + gradientUnits="userSpaceOnUse" /> + <linearGradient + gradientUnits="userSpaceOnUse" + y2="-155.75885" + x2="114.59022" + y1="35.545681" + x1="114.55434" + id="linearGradient3794" + xlink:href="#linearGradient3788" + inkscape:collect="always" /> + <linearGradient + id="linearGradient3788"> + <stop + id="stop3790" + offset="0" + style="stop-color:#1eed00;stop-opacity:1;" /> + <stop + id="stop3792" + offset="1" + style="stop-color:#abff28;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient3821"> + <stop + style="stop-color:#ff283d;stop-opacity:1;" + offset="0" + id="stop3823" /> + <stop + style="stop-color:#ff28ae;stop-opacity:1;" + offset="1" + id="stop3825" /> + </linearGradient> + <linearGradient + id="linearGradient4543"> + <stop + style="stop-color:#2e45bf;stop-opacity:1;" + offset="0" + id="stop4545" /> + <stop + style="stop-color:#28a7ff;stop-opacity:1;" + offset="1" + id="stop4547" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient4098"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop4100" /> + <stop + style="stop-color:#e6e6e6;stop-opacity:1" + offset="1" + id="stop4102" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4098" + id="linearGradient3833" + x1="273.81851" + y1="764.74677" + x2="304.14023" + y2="936.47272" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4098" + id="linearGradient3853" + gradientUnits="userSpaceOnUse" + x1="273.81851" + y1="764.74677" + x2="304.14023" + y2="936.47272" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3818" + id="radialGradient3863" + cx="262.33273" + cy="945.23846" + fx="262.33273" + fy="945.23846" + r="185.49754" + gradientTransform="matrix(1.2253203,-0.54206726,0.43090148,0.97403458,-466.4135,170.11831)" + gradientUnits="userSpaceOnUse" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3818" + id="radialGradient3866" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.2253203,-0.54206726,0.43090148,0.97403458,-466.4135,170.11831)" + cx="262.33273" + cy="945.23846" + fx="262.33273" + fy="945.23846" + r="185.49754" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3913" + id="radialGradient3873" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.3800477,1.0445431,-1.3325077,1.7605059,339.09383,-577.83938)" + cx="321.75275" + cy="386.38751" + fx="321.75275" + fy="386.38751" + r="139.95312" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3818" + id="radialGradient3880" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.2253203,-0.54206726,0.43090148,0.97403458,-466.4135,-370.24387)" + cx="262.33273" + cy="945.23846" + fx="262.33273" + fy="945.23846" + r="185.49754" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3913" + id="radialGradient3883" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.4430075,-0.63865195,0.50745433,1.1475866,-594.40824,44.803037)" + cx="262.33273" + cy="945.23846" + fx="262.33273" + fy="945.23846" + r="185.49754" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3874" + id="radialGradient3881" + cx="150.35715" + cy="236.28571" + fx="150.35715" + fy="236.28571" + r="26.887305" + gradientTransform="matrix(1,0,0,0.98671703,0,3.1385771)" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4142136" + inkscape:cx="260.34974" + inkscape:cy="246.85245" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="956" + inkscape:window-height="1039" + inkscape:window-x="960" + inkscape:window-y="18" + inkscape:window-maximized="0" + showguides="false" + inkscape:guide-bbox="true" + inkscape:snap-to-guides="true" + inkscape:snap-grids="false" + inkscape:object-paths="true" + inkscape:object-nodes="false" + inkscape:snap-nodes="false" + inkscape:pagecheckerboard="true"> + <sodipodi:guide + orientation="1,0" + position="0,534.28571" + id="guide3004" + inkscape:locked="false" /> + <sodipodi:guide + orientation="0,1" + position="394.28571,511.42857" + id="guide3006" + inkscape:locked="false" /> + <sodipodi:guide + orientation="1,0" + position="511.42857,320" + id="guide3008" + inkscape:locked="false" /> + <sodipodi:guide + orientation="0,1" + position="401.42857,0" + id="guide3010" + inkscape:locked="false" /> + <sodipodi:guide + orientation="1,0" + position="17.142857,258.57143" + id="guide3012" + inkscape:locked="false" /> + <sodipodi:guide + orientation="0,1" + position="327.14286,494.28571" + id="guide3014" + inkscape:locked="false" /> + <sodipodi:guide + orientation="0,1" + position="324.28571,17.142857" + id="guide3016" + inkscape:locked="false" /> + <sodipodi:guide + orientation="1,0" + position="494.28571,237.14286" + id="guide3018" + inkscape:locked="false" /> + <sodipodi:guide + orientation="1,0" + position="255.71429,302.85714" + id="guide3022" + inkscape:locked="false" /> + <sodipodi:guide + orientation="1,0" + position="660,-315" + id="guide3904" + inkscape:locked="false" /> + <sodipodi:guide + orientation="0,1" + position="554.28571,475.71429" + id="guide3931" + inkscape:locked="false" /> + <sodipodi:guide + orientation="0,1" + position="581.42857,244.28571" + id="guide3933" + inkscape:locked="false" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-540.36218)" + style="display:inline"> + <path + style="fill:#ffffff;fill-opacity:1;stroke:none" + d="m 253.21875,17.71875 c -127.0747,0 -230.75,101.16492 -230.75,226.03125 0,124.86632 103.66932,226.09375 230.75,226.09375 39.52056,0 69.99755,-8.10776 104.78125,-20.75 L 468.46875,493.625 c 11.02016,4.46685 22.45453,-5.45389 19.59375,-17 L 458.125,355.65625 C 477.35631,321.88611 483.9375,283.41561 483.9375,243.75 483.9375,118.88673 380.29349,17.71875 253.21875,17.71875 Z m 127.29102,163.70508 23.93164,23.93164 -141.7461,141.74609 -107.1914,-107.1914 v 72.28125 H 121.6582 v -130.0586 h 130.06055 v 33.84375 h -72.2832 l 83.25976,83.26172 z" + transform="translate(0,540.36218)" + id="path3868" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssccccsscccccccccccc" /> + <path + sodipodi:nodetypes="ccsssscc" + inkscape:connector-curvature="0" + id="path3845" + d="M 478.64112,1025.218 447.36049,898.60749 c 19.89028,-31.99834 26.74288,-69.57172 26.74288,-109.76189 0,-116.81686 -96.79943,-211.48385 -216.18374,-211.48385 -119.38425,0 -216.183656,94.66699 -216.183656,211.48385 0,116.81685 96.799406,211.5536 216.183656,211.5536 39.63617,0 68.58847,-8.14219 105.19417,-21.76075 z" + style="opacity:0;fill:none;stroke:#000000;stroke-width:23.55835724;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:94.23343197, 94.23343197;stroke-dashoffset:0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer4" + inkscape:label="Dots" /> +</svg> diff --git a/art/render.rb b/art/render.rb index 7fb46d138..7ae4ac8ae 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 4a825fbb3..8bb65cc0f 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<Message> 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 c9b932415..b6916020d 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -90,17 +90,20 @@ 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; private static final int ERROR_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 6; 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 * 12; + private static final int DELIVERY_FAILED_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 13; private final XmppConnectionService mXmppConnectionService; private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>(); private final HashMap<Conversation, AtomicInteger> mBacklogMessageCounter = new HashMap<>(); + private final LinkedHashMap<Conversational, MissedCallsInfo> mMissedCalls = new LinkedHashMap<>(); private Conversation mOpenConversation; private boolean mIsInForeground; private long mLastNotification; @@ -221,6 +224,16 @@ 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", @@ -284,12 +297,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() { @@ -320,12 +339,16 @@ 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); + } } } @@ -360,6 +383,9 @@ public class NotificationService { updateNotification(count > 0, conversations); } } + synchronized (mMissedCalls) { + updateMissedCallNotifications(mMissedCalls.keySet()); + } } private List<String> getBacklogConversations(Account account) { @@ -666,7 +692,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() @@ -695,7 +721,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<Message> messages : notifications.values()) { markAsReadIfHasDirectReply(messages); @@ -705,7 +753,7 @@ public class NotificationService { } } - public void clear(final Conversation conversation) { + public void clearMessages(final Conversation conversation) { synchronized (this.mBacklogMessageCounter) { this.mBacklogMessageCounter.remove(conversation); } @@ -718,6 +766,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())); } @@ -797,7 +864,7 @@ public class NotificationService { } modifyForSoundVibrationAndLight( singleBuilder, notifyThis, quiteHours, preferences); - singleBuilder.setGroup(CONVERSATIONS_GROUP); + singleBuilder.setGroup(MESSAGES_GROUP); setNotificationColor(singleBuilder); notify(entry.getKey(), NOTIFICATION_ID, singleBuilder.build()); } @@ -807,6 +874,31 @@ public class NotificationService { } } + private void updateMissedCallNotifications(final Set<Conversational> 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(); @@ -867,6 +959,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<Conversational, MissedCallsInfo> 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( @@ -932,7 +1119,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; @@ -1336,7 +1523,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( @@ -1356,6 +1543,16 @@ public class NotificationService { : PendingIntent.FLAG_UPDATE_CURRENT); } + 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, @@ -1677,6 +1874,28 @@ public class NotificationService { } } + 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; + } + } private class VibrationRunnable implements Runnable { @Override diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 79da6d551..245454247 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -175,7 +175,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"; @@ -670,19 +671,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; @@ -769,7 +786,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: @@ -1954,7 +1971,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 353851c37..c69fc6b02 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1110,6 +1110,7 @@ public class JingleRtpConnection extends AbstractJingleConnection rejectCallFromSessionInitiate(); break; } + xmppConnectionService.getNotificationService().pushMissedCallNow(message); } private void cancelRingingTimeout() { @@ -1187,6 +1188,7 @@ public class JingleRtpConnection extends AbstractJingleConnection 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() diff --git a/src/main/res/drawable-hdpi/ic_missed_call_notification.png b/src/main/res/drawable-hdpi/ic_missed_call_notification.png Binary files differnew file mode 100644 index 000000000..3608ebd92 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_missed_call_notification.png diff --git a/src/main/res/drawable-mdpi/ic_missed_call_notification.png b/src/main/res/drawable-mdpi/ic_missed_call_notification.png Binary files differnew file mode 100644 index 000000000..9c6c37da0 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_missed_call_notification.png diff --git a/src/main/res/drawable-xhdpi/ic_missed_call_notification.png b/src/main/res/drawable-xhdpi/ic_missed_call_notification.png Binary files differnew file mode 100644 index 000000000..80cd15819 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_missed_call_notification.png diff --git a/src/main/res/drawable-xxhdpi/ic_missed_call_notification.png b/src/main/res/drawable-xxhdpi/ic_missed_call_notification.png Binary files differnew file mode 100644 index 000000000..0072d2ef0 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_missed_call_notification.png diff --git a/src/main/res/drawable-xxxhdpi/ic_missed_call_notification.png b/src/main/res/drawable-xxxhdpi/ic_missed_call_notification.png Binary files differnew file mode 100644 index 000000000..b4343bb10 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_missed_call_notification.png diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 53fb4871d..c51be907e 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -767,6 +767,7 @@ <string name="messages_channel_name">Messages</string> <string name="incoming_calls_channel_name">Incoming calls</string> <string name="ongoing_calls_channel_name">Ongoing calls</string> + <string name="missed_calls_channel_name">Missed calls</string> <string name="silent_messages_channel_name">Silent messages</string> <string name="silent_messages_channel_description">This notification group is used to display notifications that should not trigger any sound. For example when being active on another device (Grace Period).</string> <string name="delivery_failed_channel_name">Failed deliveries</string> @@ -934,6 +935,10 @@ <string name="outgoing_call">Outgoing call</string> <string name="outgoing_call_duration">Outgoing call ยท %s</string> <string name="missed_call">Missed call</string> + <string name="missed_call_from_x">Missed call from %s</string> + <string name="n_missed_calls_from_x">%1$d missed calls from %2$s</string> + <string name="n_missed_calls">%d missed calls</string> + <string name="n_missed_calls_from_m_contacts">%1$d missed calls from %2$d contacts</string> <string name="audio_call">Audio call</string> <string name="video_call">Video call</string> <string name="help">Help</string> |