1
0
Fork 1

rework missed calls

* original from https://github.com/iNPUTmice/Conversations/pull/3857
This commit is contained in:
Christian Schneppe 2020-09-26 15:56:51 +02:00
parent 6ee5ca1da7
commit cd230f49ab
15 changed files with 262 additions and 22 deletions

View file

@ -247,11 +247,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);

View file

@ -86,7 +86,8 @@ public class NotificationService {
private static final int CALL_DAT = 120;
private static final long[] CALL_PATTERN = {0, 3 * CALL_DAT, CALL_DAT, CALL_DAT, 3 * CALL_DAT, CALL_DAT, CALL_DAT};
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;
public static final int NOTIFICATION_ID = 2 * NOTIFICATION_ID_MULTIPLIER;
public static final int FOREGROUND_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 4;
@ -95,8 +96,10 @@ 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<String, ArrayList<Message>> notifications = new LinkedHashMap<>();
private final LinkedHashMap<Conversational, MissedCallsInfo> mMissedCalls = new LinkedHashMap<>();
private final HashMap<Conversation, AtomicInteger> mBacklogMessageCounter = new HashMap<>();
private Conversation mOpenConversation;
private boolean mIsInForeground;
@ -184,6 +187,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),
@ -235,12 +247,18 @@ public class NotificationService {
notificationManager.createNotificationChannel(deliveryFailedChannel);
}
private boolean notify(final Message message) {
public 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() {
@ -264,11 +282,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);
}
}
}
@ -303,6 +325,9 @@ public class NotificationService {
updateNotification(count > 0, conversations);
}
}
synchronized (mMissedCalls) {
updateMissedCallNotifications(mMissedCalls.keySet());
}
}
private List<String> getBacklogConversations(Account account) {
@ -494,7 +519,7 @@ public class NotificationService {
private void pushNow(final Message message) {
mXmppConnectionService.updateUnreadCountBadge();
final boolean isScreenOn = mXmppConnectionService.isInteractive();
if (!notify(message)) {
if (!notifyMessage(message)) {
if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) {
mXmppConnectionService.vibrate();
}
@ -520,7 +545,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);
@ -530,7 +577,7 @@ public class NotificationService {
}
}
public void clear(final Conversation conversation) {
public void clearMessages(final Conversation conversation) {
synchronized (this.mBacklogMessageCounter) {
this.mBacklogMessageCounter.remove(conversation);
}
@ -543,6 +590,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()));
}
@ -606,7 +672,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());
}
@ -616,6 +682,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();
final String ringtone = preferences.getString("notification_ringtone", resources.getString(R.string.notification_ringtone));
@ -674,6 +765,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(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
@ -710,7 +896,7 @@ public class NotificationService {
mBuilder.setContentIntent(createOpenConversationsIntent());
}
mBuilder.setGroupSummary(true);
mBuilder.setGroup(CONVERSATIONS_GROUP);
mBuilder.setGroup(MESSAGES_GROUP);
mBuilder.setDeleteIntent(createDeleteIntent(null));
mBuilder.setSmallIcon(R.drawable.ic_notification);
return mBuilder;
@ -1015,7 +1201,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);
@ -1023,6 +1209,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(Conversation conversation, boolean dismissAfterReply) {
final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION);
@ -1314,4 +1510,27 @@ public class NotificationService {
Log.d(Config.LOGTAG, "unable to cancel notification", e);
}
}
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;
}
}
}

View file

@ -181,7 +181,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";
@ -661,19 +662,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;
@ -765,7 +782,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:
@ -2083,7 +2100,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(message -> mNotificationService.pushFromBacklog(message));
conversation.findUnreadMessagesAndCalls(message -> mNotificationService.pushFromBacklog(message));
}
public void loadPhoneContacts() {

View file

@ -582,6 +582,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
rejectCallFromSessionInitiate();
break;
}
xmppConnectionService.getNotificationService().pushMissedCallNow(message);
}
private void cancelRingingTimeout() {
@ -624,6 +625,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);
@ -1235,9 +1237,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
private void writeLogMessageMissed() {
this.message.setBody(new RtpSessionStatus(false, 0).toString());
if (this.state != State.REJECTED) {
xmppConnectionService.getNotificationService().push(message);
}
this.writeMessage();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 825 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 B

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1059,4 +1059,9 @@
<item quantity="other">Some messages could not be delivered</item>
</plurals>
<string name="failed_deliveries">Failed deliveries</string>
<string name="missed_calls_channel_name">Missed calls</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>
</resources>