2014-09-28 13:21:56 +00:00
package eu.siacs.conversations.services ;
import android.app.Notification ;
2018-09-05 19:37:05 +00:00
import android.app.NotificationChannel ;
import android.app.NotificationChannelGroup ;
import android.app.NotificationManager ;
2014-09-28 13:21:56 +00:00
import android.app.PendingIntent ;
2018-09-05 19:37:05 +00:00
import android.content.Context ;
2014-09-28 13:21:56 +00:00
import android.content.Intent ;
import android.content.SharedPreferences ;
2017-08-22 10:19:18 +00:00
import android.content.res.Resources ;
2014-10-24 12:34:46 +00:00
import android.graphics.Bitmap ;
2017-03-06 02:31:15 +00:00
import android.graphics.Typeface ;
2018-09-05 19:37:05 +00:00
import android.media.AudioAttributes ;
import android.media.RingtoneManager ;
2014-09-28 13:21:56 +00:00
import android.net.Uri ;
2015-01-14 03:46:06 +00:00
import android.os.Build ;
2014-10-24 11:29:18 +00:00
import android.os.SystemClock ;
2017-07-01 11:41:24 +00:00
import android.preference.PreferenceManager ;
2018-09-05 19:37:05 +00:00
import android.support.annotation.RequiresApi ;
2014-09-28 13:21:56 +00:00
import android.support.v4.app.NotificationCompat ;
2014-10-24 16:27:53 +00:00
import android.support.v4.app.NotificationCompat.BigPictureStyle ;
import android.support.v4.app.NotificationCompat.Builder ;
2016-08-31 15:02:42 +00:00
import android.support.v4.app.NotificationManagerCompat ;
2019-01-02 14:29:25 +00:00
import android.support.v4.app.Person ;
2016-08-25 15:30:44 +00:00
import android.support.v4.app.RemoteInput ;
2017-03-29 16:30:28 +00:00
import android.support.v4.content.ContextCompat ;
2019-01-02 14:29:25 +00:00
import android.support.v4.graphics.drawable.IconCompat ;
2017-03-06 02:31:15 +00:00
import android.text.SpannableString ;
import android.text.style.StyleSpan ;
2014-10-21 12:57:16 +00:00
import android.util.DisplayMetrics ;
2016-06-19 09:04:59 +00:00
import android.util.Log ;
2014-09-28 13:21:56 +00:00
2017-05-13 06:10:47 +00:00
import java.io.File ;
2018-05-07 09:13:46 +00:00
import java.io.IOException ;
2014-11-05 20:37:40 +00:00
import java.util.ArrayList ;
2014-12-14 07:02:17 +00:00
import java.util.Calendar ;
2018-09-09 19:34:41 +00:00
import java.util.Collections ;
2017-03-23 14:34:52 +00:00
import java.util.HashMap ;
import java.util.Iterator ;
2014-11-05 20:37:40 +00:00
import java.util.LinkedHashMap ;
2014-11-18 14:26:28 +00:00
import java.util.List ;
2016-08-25 13:20:06 +00:00
import java.util.Map ;
2020-04-15 16:28:04 +00:00
import java.util.Set ;
2017-03-23 14:34:52 +00:00
import java.util.concurrent.atomic.AtomicInteger ;
2014-11-05 20:37:40 +00:00
import java.util.regex.Matcher ;
import java.util.regex.Pattern ;
2014-10-24 11:29:18 +00:00
import eu.siacs.conversations.Config ;
2014-09-28 13:21:56 +00:00
import eu.siacs.conversations.R ;
2014-10-17 09:01:38 +00:00
import eu.siacs.conversations.entities.Account ;
2016-08-26 11:35:01 +00:00
import eu.siacs.conversations.entities.Contact ;
2014-09-28 13:21:56 +00:00
import eu.siacs.conversations.entities.Conversation ;
2018-04-26 11:22:31 +00:00
import eu.siacs.conversations.entities.Conversational ;
2014-09-28 13:21:56 +00:00
import eu.siacs.conversations.entities.Message ;
2017-05-13 06:10:47 +00:00
import eu.siacs.conversations.persistance.FileBackend ;
2018-03-07 18:05:28 +00:00
import eu.siacs.conversations.ui.ConversationsActivity ;
2018-10-09 17:35:33 +00:00
import eu.siacs.conversations.ui.EditAccountActivity ;
2020-04-07 16:50:39 +00:00
import eu.siacs.conversations.ui.RtpSessionActivity ;
2014-12-14 07:02:17 +00:00
import eu.siacs.conversations.ui.TimePreference ;
2018-10-09 17:35:33 +00:00
import eu.siacs.conversations.utils.AccountUtils ;
2018-09-05 19:37:05 +00:00
import eu.siacs.conversations.utils.Compatibility ;
2015-03-10 20:46:13 +00:00
import eu.siacs.conversations.utils.GeoHelper ;
2015-01-12 15:09:39 +00:00
import eu.siacs.conversations.utils.UIHelper ;
2017-04-30 14:19:39 +00:00
import eu.siacs.conversations.xmpp.XmppConnection ;
2020-04-07 16:50:39 +00:00
import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection ;
2020-04-15 16:28:04 +00:00
import eu.siacs.conversations.xmpp.jingle.Media ;
2014-09-28 13:21:56 +00:00
public class NotificationService {
2018-09-05 19:37:05 +00:00
public static final Object CATCHUP_LOCK = new Object ( ) ;
2018-10-02 18:25:03 +00:00
private static final int LED_COLOR = 0xff00ff00 ;
2018-09-05 19:37:05 +00:00
private static final String CONVERSATIONS_GROUP = " eu.siacs.conversations " ;
private static final int NOTIFICATION_ID_MULTIPLIER = 1024 * 1024 ;
2018-11-11 08:54:52 +00:00
static final int FOREGROUND_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 4 ;
2020-04-07 16:50:39 +00:00
private static final int NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 2 ;
2018-09-05 19:37:05 +00:00
private static final int ERROR_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 6 ;
2020-04-07 16:50:39 +00:00
private static final int INCOMING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 8 ;
2020-04-11 17:05:07 +00:00
public static final int ONGOING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 10 ;
2018-09-05 19:37:05 +00:00
private final XmppConnectionService mXmppConnectionService ;
private final LinkedHashMap < String , ArrayList < Message > > notifications = new LinkedHashMap < > ( ) ;
private final HashMap < Conversation , AtomicInteger > mBacklogMessageCounter = new HashMap < > ( ) ;
private Conversation mOpenConversation ;
private boolean mIsInForeground ;
private long mLastNotification ;
NotificationService ( final XmppConnectionService service ) {
this . mXmppConnectionService = service ;
}
private static boolean displaySnoozeAction ( List < Message > messages ) {
int numberOfMessagesWithoutReply = 0 ;
for ( Message message : messages ) {
if ( message . getStatus ( ) = = Message . STATUS_RECEIVED ) {
+ + numberOfMessagesWithoutReply ;
} else {
return false ;
}
}
return numberOfMessagesWithoutReply > = 3 ;
}
public static Pattern generateNickHighlightPattern ( final String nick ) {
2018-10-25 13:33:35 +00:00
return Pattern . compile ( " (?<=(^| \\ s)) " + Pattern . quote ( nick ) + " (?= \\ s|$| \\ p{Punct}) " ) ;
2018-09-05 19:37:05 +00:00
}
2020-04-07 16:50:39 +00:00
private static boolean isImageMessage ( Message message ) {
return message . getType ( ) ! = Message . TYPE_TEXT
& & message . getTransferable ( ) = = null
& & ! message . isDeleted ( )
& & message . getEncryption ( ) ! = Message . ENCRYPTION_PGP
& & message . getFileParams ( ) . height > 0 ;
}
2018-09-05 19:37:05 +00:00
@RequiresApi ( api = Build . VERSION_CODES . O )
2018-10-25 20:06:41 +00:00
void initializeChannels ( ) {
2018-09-05 19:37:05 +00:00
final Context c = mXmppConnectionService ;
2018-09-16 12:40:28 +00:00
final NotificationManager notificationManager = c . getSystemService ( NotificationManager . class ) ;
2018-09-05 19:37:05 +00:00
if ( notificationManager = = null ) {
return ;
}
2019-01-22 18:25:45 +00:00
notificationManager . deleteNotificationChannel ( " export " ) ;
2018-09-05 19:37:05 +00:00
notificationManager . createNotificationChannelGroup ( new NotificationChannelGroup ( " status " , c . getString ( R . string . notification_group_status_information ) ) ) ;
notificationManager . createNotificationChannelGroup ( new NotificationChannelGroup ( " chats " , c . getString ( R . string . notification_group_messages ) ) ) ;
2020-04-07 16:50:39 +00:00
notificationManager . createNotificationChannelGroup ( new NotificationChannelGroup ( " calls " , c . getString ( R . string . notification_group_calls ) ) ) ;
2018-09-05 19:37:05 +00:00
final NotificationChannel foregroundServiceChannel = new NotificationChannel ( " foreground " ,
c . getString ( R . string . foreground_service_channel_name ) ,
NotificationManager . IMPORTANCE_MIN ) ;
foregroundServiceChannel . setDescription ( c . getString ( R . string . foreground_service_channel_description ) ) ;
foregroundServiceChannel . setShowBadge ( false ) ;
foregroundServiceChannel . setGroup ( " status " ) ;
notificationManager . createNotificationChannel ( foregroundServiceChannel ) ;
final NotificationChannel errorChannel = new NotificationChannel ( " error " ,
c . getString ( R . string . error_channel_name ) ,
NotificationManager . IMPORTANCE_LOW ) ;
errorChannel . setDescription ( c . getString ( R . string . error_channel_description ) ) ;
errorChannel . setShowBadge ( false ) ;
errorChannel . setGroup ( " status " ) ;
notificationManager . createNotificationChannel ( errorChannel ) ;
2018-09-06 13:37:31 +00:00
final NotificationChannel videoCompressionChannel = new NotificationChannel ( " compression " ,
c . getString ( R . string . video_compression_channel_name ) ,
NotificationManager . IMPORTANCE_LOW ) ;
videoCompressionChannel . setShowBadge ( false ) ;
videoCompressionChannel . setGroup ( " status " ) ;
notificationManager . createNotificationChannel ( videoCompressionChannel ) ;
2019-01-22 18:25:45 +00:00
final NotificationChannel exportChannel = new NotificationChannel ( " backup " ,
c . getString ( R . string . backup_channel_name ) ,
2018-09-16 12:40:28 +00:00
NotificationManager . IMPORTANCE_LOW ) ;
exportChannel . setShowBadge ( false ) ;
exportChannel . setGroup ( " status " ) ;
notificationManager . createNotificationChannel ( exportChannel ) ;
2020-04-07 16:50:39 +00:00
final NotificationChannel incomingCallsChannel = new NotificationChannel ( " incoming_calls " ,
c . getString ( R . string . incoming_calls_channel_name ) ,
NotificationManager . IMPORTANCE_HIGH ) ;
incomingCallsChannel . setSound ( RingtoneManager . getDefaultUri ( RingtoneManager . TYPE_RINGTONE ) , new AudioAttributes . Builder ( )
. setContentType ( AudioAttributes . CONTENT_TYPE_SONIFICATION )
. setUsage ( AudioAttributes . USAGE_NOTIFICATION_RINGTONE )
. build ( ) ) ;
incomingCallsChannel . setShowBadge ( false ) ;
incomingCallsChannel . setLightColor ( LED_COLOR ) ;
incomingCallsChannel . enableLights ( true ) ;
incomingCallsChannel . setGroup ( " calls " ) ;
notificationManager . createNotificationChannel ( incomingCallsChannel ) ;
2020-04-10 13:19:56 +00:00
final NotificationChannel ongoingCallsChannel = new NotificationChannel ( " ongoing_calls " ,
c . getString ( R . string . ongoing_calls_channel_name ) ,
NotificationManager . IMPORTANCE_LOW ) ;
ongoingCallsChannel . setShowBadge ( false ) ;
ongoingCallsChannel . setGroup ( " calls " ) ;
notificationManager . createNotificationChannel ( ongoingCallsChannel ) ;
2020-04-07 16:50:39 +00:00
2018-09-05 19:37:05 +00:00
final NotificationChannel messagesChannel = new NotificationChannel ( " messages " ,
c . getString ( R . string . messages_channel_name ) ,
NotificationManager . IMPORTANCE_HIGH ) ;
messagesChannel . setShowBadge ( true ) ;
messagesChannel . setSound ( RingtoneManager . getDefaultUri ( RingtoneManager . TYPE_NOTIFICATION ) , new AudioAttributes . Builder ( )
. setContentType ( AudioAttributes . CONTENT_TYPE_SONIFICATION )
2018-11-27 09:41:13 +00:00
. setUsage ( AudioAttributes . USAGE_NOTIFICATION_COMMUNICATION_INSTANT )
2018-09-05 19:37:05 +00:00
. build ( ) ) ;
2018-10-02 18:25:03 +00:00
messagesChannel . setLightColor ( LED_COLOR ) ;
2018-09-05 19:37:05 +00:00
final int dat = 70 ;
final long [ ] pattern = { 0 , 3 * dat , dat , dat } ;
messagesChannel . setVibrationPattern ( pattern ) ;
messagesChannel . enableVibration ( true ) ;
messagesChannel . enableLights ( true ) ;
messagesChannel . setGroup ( " chats " ) ;
notificationManager . createNotificationChannel ( messagesChannel ) ;
final NotificationChannel silentMessagesChannel = new NotificationChannel ( " silent_messages " ,
c . getString ( R . string . silent_messages_channel_name ) ,
NotificationManager . IMPORTANCE_LOW ) ;
silentMessagesChannel . setDescription ( c . getString ( R . string . silent_messages_channel_description ) ) ;
silentMessagesChannel . setShowBadge ( true ) ;
2018-10-02 18:25:03 +00:00
silentMessagesChannel . setLightColor ( LED_COLOR ) ;
2018-09-05 19:37:05 +00:00
silentMessagesChannel . enableLights ( true ) ;
silentMessagesChannel . setGroup ( " chats " ) ;
notificationManager . createNotificationChannel ( silentMessagesChannel ) ;
2018-10-02 18:25:03 +00:00
final NotificationChannel quietHoursChannel = new NotificationChannel ( " quiet_hours " ,
c . getString ( R . string . title_pref_quiet_hours ) ,
NotificationManager . IMPORTANCE_LOW ) ;
quietHoursChannel . setShowBadge ( true ) ;
quietHoursChannel . setLightColor ( LED_COLOR ) ;
quietHoursChannel . enableLights ( true ) ;
quietHoursChannel . setGroup ( " chats " ) ;
quietHoursChannel . enableVibration ( false ) ;
quietHoursChannel . setSound ( null , null ) ;
notificationManager . createNotificationChannel ( quietHoursChannel ) ;
2018-09-05 19:37:05 +00:00
}
public boolean notify ( final Message message ) {
final Conversation conversation = ( Conversation ) message . getConversation ( ) ;
return message . getStatus ( ) = = Message . STATUS_RECEIVED
& & ! conversation . isMuted ( )
& & ( conversation . alwaysNotify ( ) | | wasHighlightedOrPrivate ( message ) )
2018-10-02 18:25:03 +00:00
& & ( ! conversation . isWithStranger ( ) | | notificationsFromStrangers ( ) ) ;
2018-09-05 19:37:05 +00:00
}
2020-04-20 10:32:56 +00:00
public boolean notificationsFromStrangers ( ) {
2018-09-05 19:37:05 +00:00
return mXmppConnectionService . getBooleanPreference ( " notifications_from_strangers " , R . bool . notifications_from_strangers ) ;
}
private boolean isQuietHours ( ) {
if ( ! mXmppConnectionService . getBooleanPreference ( " enable_quiet_hours " , R . bool . enable_quiet_hours ) ) {
return false ;
}
final SharedPreferences preferences = PreferenceManager . getDefaultSharedPreferences ( mXmppConnectionService ) ;
2018-11-21 17:14:02 +00:00
final long startTime = TimePreference . minutesToTimestamp ( preferences . getLong ( " quiet_hours_start " , TimePreference . DEFAULT_VALUE ) ) ;
final long endTime = TimePreference . minutesToTimestamp ( preferences . getLong ( " quiet_hours_end " , TimePreference . DEFAULT_VALUE ) ) ;
final long nowTime = Calendar . getInstance ( ) . getTimeInMillis ( ) ;
2018-09-05 19:37:05 +00:00
if ( endTime < startTime ) {
return nowTime > startTime | | nowTime < endTime ;
} else {
return nowTime > startTime & & nowTime < endTime ;
}
}
public void pushFromBacklog ( final Message message ) {
if ( notify ( message ) ) {
synchronized ( notifications ) {
getBacklogMessageCounter ( ( Conversation ) message . getConversation ( ) ) . incrementAndGet ( ) ;
pushToStack ( message ) ;
}
}
}
private AtomicInteger getBacklogMessageCounter ( Conversation conversation ) {
synchronized ( mBacklogMessageCounter ) {
if ( ! mBacklogMessageCounter . containsKey ( conversation ) ) {
mBacklogMessageCounter . put ( conversation , new AtomicInteger ( 0 ) ) ;
}
return mBacklogMessageCounter . get ( conversation ) ;
}
}
2018-11-11 08:54:52 +00:00
void pushFromDirectReply ( final Message message ) {
2018-09-05 19:37:05 +00:00
synchronized ( notifications ) {
pushToStack ( message ) ;
updateNotification ( false ) ;
}
}
public void finishBacklog ( boolean notify , Account account ) {
synchronized ( notifications ) {
mXmppConnectionService . updateUnreadCountBadge ( ) ;
if ( account = = null | | ! notify ) {
updateNotification ( notify ) ;
} else {
2018-09-09 19:34:41 +00:00
final int count ;
final List < String > conversations ;
synchronized ( this . mBacklogMessageCounter ) {
conversations = getBacklogConversations ( account ) ;
count = getBacklogMessageCount ( account ) ;
}
updateNotification ( count > 0 , conversations ) ;
}
}
}
private List < String > getBacklogConversations ( Account account ) {
final List < String > conversations = new ArrayList < > ( ) ;
2018-11-11 08:54:52 +00:00
for ( Map . Entry < Conversation , AtomicInteger > entry : mBacklogMessageCounter . entrySet ( ) ) {
2018-09-09 19:34:41 +00:00
if ( entry . getKey ( ) . getAccount ( ) = = account ) {
conversations . add ( entry . getKey ( ) . getUuid ( ) ) ;
2018-09-05 19:37:05 +00:00
}
}
2018-09-09 19:34:41 +00:00
return conversations ;
2018-09-05 19:37:05 +00:00
}
private int getBacklogMessageCount ( Account account ) {
int count = 0 ;
2018-09-09 19:34:41 +00:00
for ( Iterator < Map . Entry < Conversation , AtomicInteger > > it = mBacklogMessageCounter . entrySet ( ) . iterator ( ) ; it . hasNext ( ) ; ) {
Map . Entry < Conversation , AtomicInteger > entry = it . next ( ) ;
if ( entry . getKey ( ) . getAccount ( ) = = account ) {
count + = entry . getValue ( ) . get ( ) ;
it . remove ( ) ;
2018-09-05 19:37:05 +00:00
}
}
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : backlog message count= " + count ) ;
return count ;
}
2018-11-11 08:54:52 +00:00
void finishBacklog ( boolean notify ) {
2018-09-05 19:37:05 +00:00
finishBacklog ( notify , null ) ;
}
private void pushToStack ( final Message message ) {
final String conversationUuid = message . getConversationUuid ( ) ;
if ( notifications . containsKey ( conversationUuid ) ) {
notifications . get ( conversationUuid ) . add ( message ) ;
} else {
final ArrayList < Message > mList = new ArrayList < > ( ) ;
mList . add ( message ) ;
notifications . put ( conversationUuid , mList ) ;
}
}
public void push ( final Message message ) {
synchronized ( CATCHUP_LOCK ) {
final XmppConnection connection = message . getConversation ( ) . getAccount ( ) . getXmppConnection ( ) ;
if ( connection ! = null & & connection . isWaitingForSmCatchup ( ) ) {
connection . incrementSmCatchupMessageCounter ( ) ;
pushFromBacklog ( message ) ;
} else {
pushNow ( message ) ;
}
}
}
2020-04-15 16:28:04 +00:00
public void showIncomingCallNotification ( final AbstractJingleConnection . Id id , final Set < Media > media ) {
2020-04-07 16:50:39 +00:00
final Intent fullScreenIntent = new Intent ( mXmppConnectionService , RtpSessionActivity . class ) ;
fullScreenIntent . putExtra ( RtpSessionActivity . EXTRA_ACCOUNT , id . account . getJid ( ) . asBareJid ( ) . toEscapedString ( ) ) ;
fullScreenIntent . putExtra ( RtpSessionActivity . EXTRA_WITH , id . with . toEscapedString ( ) ) ;
fullScreenIntent . putExtra ( RtpSessionActivity . EXTRA_SESSION_ID , id . sessionId ) ;
fullScreenIntent . addFlags ( Intent . FLAG_ACTIVITY_NEW_TASK ) ;
fullScreenIntent . addFlags ( Intent . FLAG_ACTIVITY_CLEAR_TOP ) ;
final NotificationCompat . Builder builder = new NotificationCompat . Builder ( mXmppConnectionService , " incoming_calls " ) ;
2020-04-15 16:28:04 +00:00
if ( media . contains ( Media . VIDEO ) ) {
builder . setSmallIcon ( R . drawable . ic_videocam_white_24dp ) ;
builder . setContentTitle ( mXmppConnectionService . getString ( R . string . rtp_state_incoming_video_call ) ) ;
} else {
builder . setSmallIcon ( R . drawable . ic_call_white_24dp ) ;
builder . setContentTitle ( mXmppConnectionService . getString ( R . string . rtp_state_incoming_call ) ) ;
}
2020-04-20 11:08:43 +00:00
builder . setLargeIcon ( mXmppConnectionService . getAvatarService ( ) . get (
id . getContact ( ) ,
AvatarService . getSystemUiAvatarSize ( mXmppConnectionService ) )
) ;
2020-04-07 16:50:39 +00:00
builder . setContentText ( id . account . getRoster ( ) . getContact ( id . with ) . getDisplayName ( ) ) ;
builder . setVisibility ( NotificationCompat . VISIBILITY_PUBLIC ) ;
builder . setPriority ( NotificationCompat . PRIORITY_HIGH ) ;
builder . setCategory ( NotificationCompat . CATEGORY_CALL ) ;
2020-04-16 18:26:46 +00:00
PendingIntent pendingIntent = createPendingRtpSession ( id , Intent . ACTION_VIEW , 101 ) ;
builder . setFullScreenIntent ( pendingIntent , true ) ;
builder . setContentIntent ( pendingIntent ) ; //old androids need this?
2020-04-07 16:50:39 +00:00
builder . setOngoing ( true ) ;
builder . addAction ( new NotificationCompat . Action . Builder (
R . drawable . ic_call_end_white_48dp ,
mXmppConnectionService . getString ( R . string . dismiss_call ) ,
2020-04-10 13:19:56 +00:00
createCallAction ( id . sessionId , XmppConnectionService . ACTION_DISMISS_CALL , 102 ) )
2020-04-07 16:50:39 +00:00
. build ( ) ) ;
builder . addAction ( new NotificationCompat . Action . Builder (
R . drawable . ic_call_white_24dp ,
mXmppConnectionService . getString ( R . string . answer_call ) ,
2020-04-08 07:42:06 +00:00
createPendingRtpSession ( id , RtpSessionActivity . ACTION_ACCEPT_CALL , 103 ) )
2020-04-07 16:50:39 +00:00
. build ( ) ) ;
2020-04-16 18:26:46 +00:00
modifyIncomingCall ( builder ) ;
2020-04-07 16:50:39 +00:00
final Notification notification = builder . build ( ) ;
notification . flags = notification . flags | Notification . FLAG_INSISTENT ;
2020-04-11 17:05:07 +00:00
notify ( INCOMING_CALL_NOTIFICATION_ID , notification ) ;
2020-04-07 16:50:39 +00:00
}
2020-04-15 20:40:37 +00:00
public Notification getOngoingCallNotification ( final AbstractJingleConnection . Id id , final Set < Media > media ) {
2020-04-10 13:19:56 +00:00
final NotificationCompat . Builder builder = new NotificationCompat . Builder ( mXmppConnectionService , " ongoing_calls " ) ;
2020-04-15 20:40:37 +00:00
if ( media . contains ( Media . VIDEO ) ) {
builder . setSmallIcon ( R . drawable . ic_videocam_white_24dp ) ;
builder . setContentTitle ( mXmppConnectionService . getString ( R . string . ongoing_video_call ) ) ;
} else {
builder . setSmallIcon ( R . drawable . ic_call_white_24dp ) ;
builder . setContentTitle ( mXmppConnectionService . getString ( R . string . ongoing_call ) ) ;
}
2020-04-10 13:19:56 +00:00
builder . setContentText ( id . account . getRoster ( ) . getContact ( id . with ) . getDisplayName ( ) ) ;
builder . setVisibility ( NotificationCompat . VISIBILITY_PUBLIC ) ;
builder . setPriority ( NotificationCompat . PRIORITY_HIGH ) ;
builder . setCategory ( NotificationCompat . CATEGORY_CALL ) ;
builder . setContentIntent ( createPendingRtpSession ( id , Intent . ACTION_VIEW , 101 ) ) ;
builder . setOngoing ( true ) ;
builder . addAction ( new NotificationCompat . Action . Builder (
R . drawable . ic_call_end_white_48dp ,
mXmppConnectionService . getString ( R . string . hang_up ) ,
createCallAction ( id . sessionId , XmppConnectionService . ACTION_END_CALL , 104 ) )
. build ( ) ) ;
2020-04-11 17:05:07 +00:00
return builder . build ( ) ;
2020-04-10 13:19:56 +00:00
}
2020-04-07 16:50:39 +00:00
private PendingIntent createPendingRtpSession ( final AbstractJingleConnection . Id id , final String action , final int requestCode ) {
final Intent fullScreenIntent = new Intent ( mXmppConnectionService , RtpSessionActivity . class ) ;
fullScreenIntent . setAction ( action ) ;
fullScreenIntent . putExtra ( RtpSessionActivity . EXTRA_ACCOUNT , id . account . getJid ( ) . asBareJid ( ) . toEscapedString ( ) ) ;
fullScreenIntent . putExtra ( RtpSessionActivity . EXTRA_WITH , id . with . toEscapedString ( ) ) ;
fullScreenIntent . putExtra ( RtpSessionActivity . EXTRA_SESSION_ID , id . sessionId ) ;
return PendingIntent . getActivity ( mXmppConnectionService , requestCode , fullScreenIntent , PendingIntent . FLAG_UPDATE_CURRENT ) ;
}
public void cancelIncomingCallNotification ( ) {
cancel ( INCOMING_CALL_NOTIFICATION_ID ) ;
}
2020-04-10 13:19:56 +00:00
public void cancelOngoingCallNotification ( ) {
cancel ( ONGOING_CALL_NOTIFICATION_ID ) ;
}
2018-09-05 19:37:05 +00:00
private void pushNow ( final Message message ) {
mXmppConnectionService . updateUnreadCountBadge ( ) ;
if ( ! notify ( message ) ) {
Log . d ( Config . LOGTAG , message . getConversation ( ) . getAccount ( ) . getJid ( ) . asBareJid ( ) + " : suppressing notification because turned off " ) ;
return ;
}
final boolean isScreenOn = mXmppConnectionService . isInteractive ( ) ;
if ( this . mIsInForeground & & isScreenOn & & this . mOpenConversation = = message . getConversation ( ) ) {
Log . d ( Config . LOGTAG , message . getConversation ( ) . getAccount ( ) . getJid ( ) . asBareJid ( ) + " : suppressing notification because conversation is open " ) ;
return ;
}
synchronized ( notifications ) {
pushToStack ( message ) ;
2018-09-09 19:34:41 +00:00
final Conversational conversation = message . getConversation ( ) ;
final Account account = conversation . getAccount ( ) ;
2018-09-05 19:37:05 +00:00
final boolean doNotify = ( ! ( this . mIsInForeground & & this . mOpenConversation = = null ) | | ! isScreenOn )
& & ! account . inGracePeriod ( )
& & ! this . inMiniGracePeriod ( account ) ;
2018-09-09 19:34:41 +00:00
updateNotification ( doNotify , Collections . singletonList ( conversation . getUuid ( ) ) ) ;
2018-09-05 19:37:05 +00:00
}
}
public void clear ( ) {
synchronized ( notifications ) {
for ( ArrayList < Message > messages : notifications . values ( ) ) {
markAsReadIfHasDirectReply ( messages ) ;
}
notifications . clear ( ) ;
updateNotification ( false ) ;
}
}
public void clear ( final Conversation conversation ) {
synchronized ( this . mBacklogMessageCounter ) {
this . mBacklogMessageCounter . remove ( conversation ) ;
}
synchronized ( notifications ) {
markAsReadIfHasDirectReply ( conversation ) ;
if ( notifications . remove ( conversation . getUuid ( ) ) ! = null ) {
cancel ( conversation . getUuid ( ) , NOTIFICATION_ID ) ;
2018-09-09 19:34:41 +00:00
updateNotification ( false , null , true ) ;
2018-09-05 19:37:05 +00:00
}
}
}
private void markAsReadIfHasDirectReply ( final Conversation conversation ) {
markAsReadIfHasDirectReply ( notifications . get ( conversation . getUuid ( ) ) ) ;
}
private void markAsReadIfHasDirectReply ( final ArrayList < Message > messages ) {
if ( messages ! = null & & messages . size ( ) > 0 ) {
Message last = messages . get ( messages . size ( ) - 1 ) ;
if ( last . getStatus ( ) ! = Message . STATUS_RECEIVED ) {
if ( mXmppConnectionService . markRead ( ( Conversation ) last . getConversation ( ) , false ) ) {
mXmppConnectionService . updateConversationUi ( ) ;
}
}
}
}
private void setNotificationColor ( final Builder mBuilder ) {
mBuilder . setColor ( ContextCompat . getColor ( mXmppConnectionService , R . color . green600 ) ) ;
}
2019-01-12 07:55:46 +00:00
public void updateNotification ( ) {
synchronized ( notifications ) {
updateNotification ( false ) ;
}
}
private void updateNotification ( final boolean notify ) {
2018-09-09 19:34:41 +00:00
updateNotification ( notify , null , false ) ;
}
2018-11-11 08:54:52 +00:00
private void updateNotification ( final boolean notify , final List < String > conversations ) {
2018-09-09 19:34:41 +00:00
updateNotification ( notify , conversations , false ) ;
2018-09-05 19:37:05 +00:00
}
2018-09-09 19:34:41 +00:00
private void updateNotification ( final boolean notify , final List < String > conversations , final boolean summaryOnly ) {
2018-09-05 19:37:05 +00:00
final SharedPreferences preferences = PreferenceManager . getDefaultSharedPreferences ( mXmppConnectionService ) ;
2018-10-02 18:25:03 +00:00
final boolean quiteHours = isQuietHours ( ) ;
2018-09-09 19:34:41 +00:00
final boolean notifyOnlyOneChild = notify & & conversations ! = null & & conversations . size ( ) = = 1 ; //if this check is changed to > 0 catchup messages will create one notification per conversation
2018-10-02 18:25:03 +00:00
2018-09-05 19:37:05 +00:00
if ( notifications . size ( ) = = 0 ) {
cancel ( NOTIFICATION_ID ) ;
} else {
if ( notify ) {
this . markLastNotification ( ) ;
}
final Builder mBuilder ;
if ( notifications . size ( ) = = 1 & & Build . VERSION . SDK_INT < Build . VERSION_CODES . N ) {
2018-10-02 18:25:03 +00:00
mBuilder = buildSingleConversations ( notifications . values ( ) . iterator ( ) . next ( ) , notify , quiteHours ) ;
modifyForSoundVibrationAndLight ( mBuilder , notify , quiteHours , preferences ) ;
2018-09-05 19:37:05 +00:00
notify ( NOTIFICATION_ID , mBuilder . build ( ) ) ;
} else {
2018-10-02 18:25:03 +00:00
mBuilder = buildMultipleConversation ( notify , quiteHours ) ;
2018-09-09 19:34:41 +00:00
if ( notifyOnlyOneChild ) {
mBuilder . setGroupAlertBehavior ( NotificationCompat . GROUP_ALERT_CHILDREN ) ;
}
2018-10-02 18:25:03 +00:00
modifyForSoundVibrationAndLight ( mBuilder , notify , quiteHours , preferences ) ;
2018-09-05 19:37:05 +00:00
if ( ! summaryOnly ) {
for ( Map . Entry < String , ArrayList < Message > > entry : notifications . entrySet ( ) ) {
2018-09-09 19:34:41 +00:00
String uuid = entry . getKey ( ) ;
2018-10-25 20:06:41 +00:00
final boolean notifyThis = notifyOnlyOneChild ? conversations . contains ( uuid ) : notify ;
Builder singleBuilder = buildSingleConversations ( entry . getValue ( ) , notifyThis , quiteHours ) ;
2018-09-09 19:34:41 +00:00
if ( ! notifyOnlyOneChild ) {
singleBuilder . setGroupAlertBehavior ( NotificationCompat . GROUP_ALERT_SUMMARY ) ;
}
2018-10-02 18:25:03 +00:00
modifyForSoundVibrationAndLight ( singleBuilder , notifyThis , quiteHours , preferences ) ;
2018-09-05 19:37:05 +00:00
singleBuilder . setGroup ( CONVERSATIONS_GROUP ) ;
setNotificationColor ( singleBuilder ) ;
notify ( entry . getKey ( ) , NOTIFICATION_ID , singleBuilder . build ( ) ) ;
}
}
notify ( NOTIFICATION_ID , mBuilder . build ( ) ) ;
}
}
}
2018-10-02 18:25:03 +00:00
private void modifyForSoundVibrationAndLight ( Builder mBuilder , boolean notify , boolean quietHours , SharedPreferences preferences ) {
2018-09-05 19:37:05 +00:00
final Resources resources = mXmppConnectionService . getResources ( ) ;
final String ringtone = preferences . getString ( " notification_ringtone " , resources . getString ( R . string . notification_ringtone ) ) ;
final boolean vibrate = preferences . getBoolean ( " vibrate_on_notification " , resources . getBoolean ( R . bool . vibrate_on_notification ) ) ;
final boolean led = preferences . getBoolean ( " led " , resources . getBoolean ( R . bool . led ) ) ;
final boolean headsup = preferences . getBoolean ( " notification_headsup " , resources . getBoolean ( R . bool . headsup_notifications ) ) ;
2018-10-02 18:25:03 +00:00
if ( notify & & ! quietHours ) {
2018-09-05 19:37:05 +00:00
if ( vibrate ) {
final int dat = 70 ;
final long [ ] pattern = { 0 , 3 * dat , dat , dat } ;
mBuilder . setVibrate ( pattern ) ;
} else {
mBuilder . setVibrate ( new long [ ] { 0 } ) ;
}
Uri uri = Uri . parse ( ringtone ) ;
try {
mBuilder . setSound ( fixRingtoneUri ( uri ) ) ;
} catch ( SecurityException e ) {
Log . d ( Config . LOGTAG , " unable to use custom notification sound " + uri . toString ( ) ) ;
}
2019-11-01 10:03:54 +00:00
} else {
mBuilder . setLocalOnly ( true ) ;
2018-09-05 19:37:05 +00:00
}
if ( android . os . Build . VERSION . SDK_INT > = Build . VERSION_CODES . LOLLIPOP ) {
mBuilder . setCategory ( Notification . CATEGORY_MESSAGE ) ;
}
mBuilder . setPriority ( notify ? ( headsup ? NotificationCompat . PRIORITY_HIGH : NotificationCompat . PRIORITY_DEFAULT ) : NotificationCompat . PRIORITY_LOW ) ;
setNotificationColor ( mBuilder ) ;
mBuilder . setDefaults ( 0 ) ;
if ( led ) {
2018-10-02 18:25:03 +00:00
mBuilder . setLights ( LED_COLOR , 2000 , 3000 ) ;
2018-09-05 19:37:05 +00:00
}
}
2020-04-16 18:26:46 +00:00
private void modifyIncomingCall ( Builder mBuilder ) {
final SharedPreferences preferences = PreferenceManager . getDefaultSharedPreferences ( mXmppConnectionService ) ;
final Resources resources = mXmppConnectionService . getResources ( ) ;
final String ringtone = preferences . getString ( " call_ringtone " , resources . getString ( R . string . incoming_call_ringtone ) ) ;
final int dat = 70 ;
final long [ ] pattern = { 0 , 3 * dat , dat , dat , 3 * dat , dat , dat } ;
mBuilder . setVibrate ( pattern ) ;
Uri uri = Uri . parse ( ringtone ) ;
try {
mBuilder . setSound ( fixRingtoneUri ( uri ) ) ;
} catch ( SecurityException e ) {
Log . d ( Config . LOGTAG , " unable to use custom notification sound " + uri . toString ( ) ) ;
}
if ( android . os . Build . VERSION . SDK_INT > = Build . VERSION_CODES . LOLLIPOP ) {
mBuilder . setCategory ( Notification . CATEGORY_MESSAGE ) ;
}
mBuilder . setPriority ( NotificationCompat . PRIORITY_HIGH ) ;
setNotificationColor ( mBuilder ) ;
mBuilder . setLights ( LED_COLOR , 2000 , 3000 ) ;
}
2018-09-05 19:37:05 +00:00
private Uri fixRingtoneUri ( Uri uri ) {
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . N & & " file " . equals ( uri . getScheme ( ) ) ) {
return FileBackend . getUriForFile ( mXmppConnectionService , new File ( uri . getPath ( ) ) ) ;
} else {
return uri ;
}
}
2018-10-02 18:25:03 +00:00
private Builder buildMultipleConversation ( final boolean notify , final boolean quietHours ) {
final Builder mBuilder = new NotificationCompat . Builder ( mXmppConnectionService , quietHours ? " quiet_hours " : ( notify ? " messages " : " silent_messages " ) ) ;
2018-09-05 19:37:05 +00:00
final NotificationCompat . InboxStyle style = new NotificationCompat . InboxStyle ( ) ;
2020-04-07 16:50:39 +00:00
style . setBigContentTitle ( mXmppConnectionService . getString ( R . string . x_unread_conversations , notifications . size ( ) ) ) ;
2018-09-05 19:37:05 +00:00
final StringBuilder names = new StringBuilder ( ) ;
Conversation conversation = null ;
for ( final ArrayList < Message > messages : notifications . values ( ) ) {
if ( messages . size ( ) > 0 ) {
conversation = ( Conversation ) messages . get ( 0 ) . getConversation ( ) ;
final String name = conversation . getName ( ) . toString ( ) ;
SpannableString styledString ;
if ( Config . HIDE_MESSAGE_TEXT_IN_NOTIFICATION ) {
int count = messages . size ( ) ;
styledString = new SpannableString ( name + " : " + mXmppConnectionService . getResources ( ) . getQuantityString ( R . plurals . x_messages , count , count ) ) ;
styledString . setSpan ( new StyleSpan ( Typeface . BOLD ) , 0 , name . length ( ) , 0 ) ;
style . addLine ( styledString ) ;
} else {
styledString = new SpannableString ( name + " : " + UIHelper . getMessagePreview ( mXmppConnectionService , messages . get ( 0 ) ) . first ) ;
styledString . setSpan ( new StyleSpan ( Typeface . BOLD ) , 0 , name . length ( ) , 0 ) ;
style . addLine ( styledString ) ;
}
names . append ( name ) ;
names . append ( " , " ) ;
}
}
if ( names . length ( ) > = 2 ) {
names . delete ( names . length ( ) - 2 , names . length ( ) ) ;
}
2019-09-08 15:58:15 +00:00
mBuilder . setContentTitle ( mXmppConnectionService . getString ( R . string . x_unread_conversations , notifications . size ( ) ) ) ;
mBuilder . setTicker ( mXmppConnectionService . getString ( R . string . x_unread_conversations , notifications . size ( ) ) ) ;
2018-09-05 19:37:05 +00:00
mBuilder . setContentText ( names . toString ( ) ) ;
mBuilder . setStyle ( style ) ;
if ( conversation ! = null ) {
mBuilder . setContentIntent ( createContentIntent ( conversation ) ) ;
}
mBuilder . setGroupSummary ( true ) ;
mBuilder . setGroup ( CONVERSATIONS_GROUP ) ;
mBuilder . setDeleteIntent ( createDeleteIntent ( null ) ) ;
mBuilder . setSmallIcon ( R . drawable . ic_notification ) ;
return mBuilder ;
}
2018-10-02 18:25:03 +00:00
private Builder buildSingleConversations ( final ArrayList < Message > messages , final boolean notify , final boolean quietHours ) {
final Builder mBuilder = new NotificationCompat . Builder ( mXmppConnectionService , quietHours ? " quiet_hours " : ( notify ? " messages " : " silent_messages " ) ) ;
2018-09-05 19:37:05 +00:00
if ( messages . size ( ) > = 1 ) {
final Conversation conversation = ( Conversation ) messages . get ( 0 ) . getConversation ( ) ;
mBuilder . setLargeIcon ( mXmppConnectionService . getAvatarService ( )
2019-01-09 08:03:33 +00:00
. get ( conversation , AvatarService . getSystemUiAvatarSize ( mXmppConnectionService ) ) ) ;
2018-09-05 19:37:05 +00:00
mBuilder . setContentTitle ( conversation . getName ( ) ) ;
if ( Config . HIDE_MESSAGE_TEXT_IN_NOTIFICATION ) {
int count = messages . size ( ) ;
mBuilder . setContentText ( mXmppConnectionService . getResources ( ) . getQuantityString ( R . plurals . x_messages , count , count ) ) ;
} else {
Message message ;
2019-01-09 08:03:33 +00:00
//TODO starting with Android 9 we might want to put images in MessageStyle
2019-01-09 11:47:09 +00:00
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . P & & ( message = getImage ( messages ) ) ! = null ) {
2019-06-16 14:02:22 +00:00
modifyForImage ( mBuilder , message , messages ) ;
2018-09-05 19:37:05 +00:00
} else {
2019-06-16 14:02:22 +00:00
modifyForTextOnly ( mBuilder , messages ) ;
2018-09-05 19:37:05 +00:00
}
RemoteInput remoteInput = new RemoteInput . Builder ( " text_reply " ) . setLabel ( UIHelper . getMessageHint ( mXmppConnectionService , conversation ) ) . build ( ) ;
PendingIntent markAsReadPendingIntent = createReadPendingIntent ( conversation ) ;
NotificationCompat . Action markReadAction = new NotificationCompat . Action . Builder (
R . drawable . ic_drafts_white_24dp ,
mXmppConnectionService . getString ( R . string . mark_as_read ) ,
2019-06-16 14:02:22 +00:00
markAsReadPendingIntent )
. setSemanticAction ( NotificationCompat . Action . SEMANTIC_ACTION_MARK_AS_READ )
. setShowsUserInterface ( false )
. build ( ) ;
2018-09-05 19:37:05 +00:00
String replyLabel = mXmppConnectionService . getString ( R . string . reply ) ;
NotificationCompat . Action replyAction = new NotificationCompat . Action . Builder (
R . drawable . ic_send_text_offline ,
replyLabel ,
2019-06-16 14:02:22 +00:00
createReplyIntent ( conversation , false ) )
. setSemanticAction ( NotificationCompat . Action . SEMANTIC_ACTION_REPLY )
. setShowsUserInterface ( false )
. addRemoteInput ( remoteInput ) . build ( ) ;
2018-09-05 19:37:05 +00:00
NotificationCompat . Action wearReplyAction = new NotificationCompat . Action . Builder ( R . drawable . ic_wear_reply ,
replyLabel ,
createReplyIntent ( conversation , true ) ) . addRemoteInput ( remoteInput ) . build ( ) ;
mBuilder . extend ( new NotificationCompat . WearableExtender ( ) . addAction ( wearReplyAction ) ) ;
int addedActionsCount = 1 ;
mBuilder . addAction ( markReadAction ) ;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . N ) {
mBuilder . addAction ( replyAction ) ;
+ + addedActionsCount ;
}
if ( displaySnoozeAction ( messages ) ) {
String label = mXmppConnectionService . getString ( R . string . snooze ) ;
PendingIntent pendingSnoozeIntent = createSnoozeIntent ( conversation ) ;
NotificationCompat . Action snoozeAction = new NotificationCompat . Action . Builder (
R . drawable . ic_notifications_paused_white_24dp ,
label ,
pendingSnoozeIntent ) . build ( ) ;
mBuilder . addAction ( snoozeAction ) ;
+ + addedActionsCount ;
}
if ( addedActionsCount < 3 ) {
final Message firstLocationMessage = getFirstLocationMessage ( messages ) ;
if ( firstLocationMessage ! = null ) {
2019-10-23 20:33:51 +00:00
final PendingIntent pendingShowLocationIntent = createShowLocationIntent ( firstLocationMessage ) ;
if ( pendingShowLocationIntent ! = null ) {
final String label = mXmppConnectionService . getResources ( ) . getString ( R . string . show_location ) ;
NotificationCompat . Action locationAction = new NotificationCompat . Action . Builder (
R . drawable . ic_room_white_24dp ,
label ,
pendingShowLocationIntent ) . build ( ) ;
mBuilder . addAction ( locationAction ) ;
+ + addedActionsCount ;
}
2018-09-05 19:37:05 +00:00
}
}
if ( addedActionsCount < 3 ) {
Message firstDownloadableMessage = getFirstDownloadableMessage ( messages ) ;
if ( firstDownloadableMessage ! = null ) {
String label = mXmppConnectionService . getResources ( ) . getString ( R . string . download_x_file , UIHelper . getFileDescriptionString ( mXmppConnectionService , firstDownloadableMessage ) ) ;
PendingIntent pendingDownloadIntent = createDownloadIntent ( firstDownloadableMessage ) ;
NotificationCompat . Action downloadAction = new NotificationCompat . Action . Builder (
R . drawable . ic_file_download_white_24dp ,
label ,
pendingDownloadIntent ) . build ( ) ;
mBuilder . addAction ( downloadAction ) ;
+ + addedActionsCount ;
}
}
}
if ( conversation . getMode ( ) = = Conversation . MODE_SINGLE ) {
Contact contact = conversation . getContact ( ) ;
Uri systemAccount = contact . getSystemAccount ( ) ;
if ( systemAccount ! = null ) {
mBuilder . addPerson ( systemAccount . toString ( ) ) ;
}
}
mBuilder . setWhen ( conversation . getLatestMessage ( ) . getTimeSent ( ) ) ;
mBuilder . setSmallIcon ( R . drawable . ic_notification ) ;
mBuilder . setDeleteIntent ( createDeleteIntent ( conversation ) ) ;
mBuilder . setContentIntent ( createContentIntent ( conversation ) ) ;
}
return mBuilder ;
}
2019-06-16 14:02:22 +00:00
private void modifyForImage ( final Builder builder , final Message message , final ArrayList < Message > messages ) {
2018-09-05 19:37:05 +00:00
try {
2019-01-09 08:03:33 +00:00
final Bitmap bitmap = mXmppConnectionService . getFileBackend ( ) . getThumbnail ( message , getPixel ( 288 ) , false ) ;
2018-09-05 19:37:05 +00:00
final ArrayList < Message > tmp = new ArrayList < > ( ) ;
for ( final Message msg : messages ) {
if ( msg . getType ( ) = = Message . TYPE_TEXT
& & msg . getTransferable ( ) = = null ) {
tmp . add ( msg ) ;
}
}
final BigPictureStyle bigPictureStyle = new NotificationCompat . BigPictureStyle ( ) ;
bigPictureStyle . bigPicture ( bitmap ) ;
if ( tmp . size ( ) > 0 ) {
CharSequence text = getMergedBodies ( tmp ) ;
bigPictureStyle . setSummaryText ( text ) ;
builder . setContentText ( text ) ;
2019-09-08 15:58:15 +00:00
builder . setTicker ( text ) ;
2018-09-05 19:37:05 +00:00
} else {
2019-09-08 15:58:15 +00:00
final String description = UIHelper . getFileDescriptionString ( mXmppConnectionService , message ) ;
builder . setContentText ( description ) ;
builder . setTicker ( description ) ;
2018-09-05 19:37:05 +00:00
}
builder . setStyle ( bigPictureStyle ) ;
} catch ( final IOException e ) {
2019-06-16 14:02:22 +00:00
modifyForTextOnly ( builder , messages ) ;
2018-09-05 19:37:05 +00:00
}
}
2019-01-02 14:29:25 +00:00
private Person getPerson ( Message message ) {
final Contact contact = message . getContact ( ) ;
final Person . Builder builder = new Person . Builder ( ) ;
if ( contact ! = null ) {
builder . setName ( contact . getDisplayName ( ) ) ;
final Uri uri = contact . getSystemAccount ( ) ;
if ( uri ! = null ) {
builder . setUri ( uri . toString ( ) ) ;
}
} else {
builder . setName ( UIHelper . getMessageDisplayName ( message ) ) ;
}
2019-01-09 08:03:33 +00:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . P ) {
builder . setIcon ( IconCompat . createWithBitmap ( mXmppConnectionService . getAvatarService ( ) . get ( message , AvatarService . getSystemUiAvatarSize ( mXmppConnectionService ) , false ) ) ) ;
2019-01-02 14:29:25 +00:00
}
return builder . build ( ) ;
}
2020-04-07 16:50:39 +00:00
private void modifyForTextOnly ( final Builder builder , final ArrayList < Message > messages ) {
2018-09-05 19:37:05 +00:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . N ) {
final Conversation conversation = ( Conversation ) messages . get ( 0 ) . getConversation ( ) ;
2019-01-09 15:26:20 +00:00
final Person . Builder meBuilder = new Person . Builder ( ) . setName ( mXmppConnectionService . getString ( R . string . me ) ) ;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . P ) {
meBuilder . setIcon ( IconCompat . createWithBitmap ( mXmppConnectionService . getAvatarService ( ) . get ( conversation . getAccount ( ) , AvatarService . getSystemUiAvatarSize ( mXmppConnectionService ) ) ) ) ;
}
final Person me = meBuilder . build ( ) ;
NotificationCompat . MessagingStyle messagingStyle = new NotificationCompat . MessagingStyle ( me ) ;
2019-01-09 08:03:33 +00:00
final boolean multiple = conversation . getMode ( ) = = Conversation . MODE_MULTI ;
if ( multiple ) {
2018-09-05 19:37:05 +00:00
messagingStyle . setConversationTitle ( conversation . getName ( ) ) ;
}
for ( Message message : messages ) {
2019-01-02 14:29:25 +00:00
final Person sender = message . getStatus ( ) = = Message . STATUS_RECEIVED ? getPerson ( message ) : null ;
2019-01-09 11:47:09 +00:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . P & & isImageMessage ( message ) ) {
2020-04-07 16:50:39 +00:00
final Uri dataUri = FileBackend . getMediaUri ( mXmppConnectionService , mXmppConnectionService . getFileBackend ( ) . getFile ( message ) ) ;
2019-01-09 11:47:09 +00:00
NotificationCompat . MessagingStyle . Message imageMessage = new NotificationCompat . MessagingStyle . Message ( UIHelper . getMessagePreview ( mXmppConnectionService , message ) . first , message . getTimeSent ( ) , sender ) ;
if ( dataUri ! = null ) {
imageMessage . setData ( message . getMimeType ( ) , dataUri ) ;
}
messagingStyle . addMessage ( imageMessage ) ;
} else {
messagingStyle . addMessage ( UIHelper . getMessagePreview ( mXmppConnectionService , message ) . first , message . getTimeSent ( ) , sender ) ;
}
2018-09-05 19:37:05 +00:00
}
2019-01-09 08:03:33 +00:00
messagingStyle . setGroupConversation ( multiple ) ;
2018-09-05 19:37:05 +00:00
builder . setStyle ( messagingStyle ) ;
} else {
if ( messages . get ( 0 ) . getConversation ( ) . getMode ( ) = = Conversation . MODE_SINGLE ) {
builder . setStyle ( new NotificationCompat . BigTextStyle ( ) . bigText ( getMergedBodies ( messages ) ) ) ;
2020-04-07 16:50:39 +00:00
final CharSequence preview = UIHelper . getMessagePreview ( mXmppConnectionService , messages . get ( messages . size ( ) - 1 ) ) . first ;
2019-09-08 15:58:15 +00:00
builder . setContentText ( preview ) ;
builder . setTicker ( preview ) ;
2018-12-15 16:44:05 +00:00
builder . setNumber ( messages . size ( ) ) ;
2018-09-05 19:37:05 +00:00
} else {
final NotificationCompat . InboxStyle style = new NotificationCompat . InboxStyle ( ) ;
SpannableString styledString ;
for ( Message message : messages ) {
final String name = UIHelper . getMessageDisplayName ( message ) ;
styledString = new SpannableString ( name + " : " + message . getBody ( ) ) ;
styledString . setSpan ( new StyleSpan ( Typeface . BOLD ) , 0 , name . length ( ) , 0 ) ;
style . addLine ( styledString ) ;
}
builder . setStyle ( style ) ;
int count = messages . size ( ) ;
if ( count = = 1 ) {
final String name = UIHelper . getMessageDisplayName ( messages . get ( 0 ) ) ;
styledString = new SpannableString ( name + " : " + messages . get ( 0 ) . getBody ( ) ) ;
styledString . setSpan ( new StyleSpan ( Typeface . BOLD ) , 0 , name . length ( ) , 0 ) ;
builder . setContentText ( styledString ) ;
2019-09-08 15:58:15 +00:00
builder . setTicker ( styledString ) ;
2018-09-05 19:37:05 +00:00
} else {
2019-09-08 15:58:15 +00:00
final String text = mXmppConnectionService . getResources ( ) . getQuantityString ( R . plurals . x_messages , count , count ) ;
builder . setContentText ( text ) ;
builder . setTicker ( text ) ;
2018-09-05 19:37:05 +00:00
}
}
}
}
private Message getImage ( final Iterable < Message > messages ) {
Message image = null ;
for ( final Message message : messages ) {
if ( message . getStatus ( ) ! = Message . STATUS_RECEIVED ) {
return null ;
}
2019-01-09 11:47:09 +00:00
if ( isImageMessage ( message ) ) {
2018-09-05 19:37:05 +00:00
image = message ;
}
}
return image ;
}
private Message getFirstDownloadableMessage ( final Iterable < Message > messages ) {
for ( final Message message : messages ) {
if ( message . getTransferable ( ) ! = null | | ( message . getType ( ) = = Message . TYPE_TEXT & & message . treatAsDownloadable ( ) ) ) {
return message ;
}
}
return null ;
}
private Message getFirstLocationMessage ( final Iterable < Message > messages ) {
for ( final Message message : messages ) {
if ( message . isGeoUri ( ) ) {
return message ;
}
}
return null ;
}
private CharSequence getMergedBodies ( final ArrayList < Message > messages ) {
final StringBuilder text = new StringBuilder ( ) ;
for ( Message message : messages ) {
if ( text . length ( ) ! = 0 ) {
text . append ( " \ n " ) ;
}
text . append ( UIHelper . getMessagePreview ( mXmppConnectionService , message ) . first ) ;
}
return text . toString ( ) ;
}
private PendingIntent createShowLocationIntent ( final Message message ) {
Iterable < Intent > intents = GeoHelper . createGeoIntentsFromMessage ( mXmppConnectionService , message ) ;
for ( Intent intent : intents ) {
if ( intent . resolveActivity ( mXmppConnectionService . getPackageManager ( ) ) ! = null ) {
return PendingIntent . getActivity ( mXmppConnectionService , generateRequestCode ( message . getConversation ( ) , 18 ) , intent , PendingIntent . FLAG_UPDATE_CURRENT ) ;
}
}
2019-10-23 20:33:51 +00:00
return null ;
2018-09-05 19:37:05 +00:00
}
private PendingIntent createContentIntent ( final String conversationUuid , final String downloadMessageUuid ) {
final Intent viewConversationIntent = new Intent ( mXmppConnectionService , ConversationsActivity . class ) ;
viewConversationIntent . setAction ( ConversationsActivity . ACTION_VIEW_CONVERSATION ) ;
viewConversationIntent . putExtra ( ConversationsActivity . EXTRA_CONVERSATION , conversationUuid ) ;
if ( downloadMessageUuid ! = null ) {
viewConversationIntent . putExtra ( ConversationsActivity . EXTRA_DOWNLOAD_UUID , downloadMessageUuid ) ;
return PendingIntent . getActivity ( mXmppConnectionService ,
generateRequestCode ( conversationUuid , 8 ) ,
viewConversationIntent ,
PendingIntent . FLAG_UPDATE_CURRENT ) ;
} else {
return PendingIntent . getActivity ( mXmppConnectionService ,
generateRequestCode ( conversationUuid , 10 ) ,
viewConversationIntent ,
PendingIntent . FLAG_UPDATE_CURRENT ) ;
}
}
private int generateRequestCode ( String uuid , int actionId ) {
return ( actionId * NOTIFICATION_ID_MULTIPLIER ) + ( uuid . hashCode ( ) % NOTIFICATION_ID_MULTIPLIER ) ;
}
private int generateRequestCode ( Conversational conversation , int actionId ) {
return generateRequestCode ( conversation . getUuid ( ) , actionId ) ;
}
private PendingIntent createDownloadIntent ( final Message message ) {
return createContentIntent ( message . getConversationUuid ( ) , message . getUuid ( ) ) ;
}
private PendingIntent createContentIntent ( final Conversational conversation ) {
return createContentIntent ( conversation . getUuid ( ) , null ) ;
}
private PendingIntent createDeleteIntent ( Conversation conversation ) {
final Intent intent = new Intent ( mXmppConnectionService , XmppConnectionService . class ) ;
intent . setAction ( XmppConnectionService . ACTION_CLEAR_NOTIFICATION ) ;
if ( conversation ! = null ) {
intent . putExtra ( " uuid " , conversation . getUuid ( ) ) ;
return PendingIntent . getService ( mXmppConnectionService , generateRequestCode ( conversation , 20 ) , intent , 0 ) ;
}
return PendingIntent . getService ( mXmppConnectionService , 0 , intent , 0 ) ;
}
private PendingIntent createReplyIntent ( Conversation conversation , boolean dismissAfterReply ) {
final Intent intent = new Intent ( mXmppConnectionService , XmppConnectionService . class ) ;
intent . setAction ( XmppConnectionService . ACTION_REPLY_TO_CONVERSATION ) ;
intent . putExtra ( " uuid " , conversation . getUuid ( ) ) ;
intent . putExtra ( " dismiss_notification " , dismissAfterReply ) ;
final int id = generateRequestCode ( conversation , dismissAfterReply ? 12 : 14 ) ;
return PendingIntent . getService ( mXmppConnectionService , id , intent , 0 ) ;
}
private PendingIntent createReadPendingIntent ( Conversation conversation ) {
final Intent intent = new Intent ( mXmppConnectionService , XmppConnectionService . class ) ;
intent . setAction ( XmppConnectionService . ACTION_MARK_AS_READ ) ;
intent . putExtra ( " uuid " , conversation . getUuid ( ) ) ;
intent . setPackage ( mXmppConnectionService . getPackageName ( ) ) ;
return PendingIntent . getService ( mXmppConnectionService , generateRequestCode ( conversation , 16 ) , intent , PendingIntent . FLAG_UPDATE_CURRENT ) ;
}
2020-04-10 13:19:56 +00:00
private PendingIntent createCallAction ( String sessionId , final String action , int requestCode ) {
2020-04-07 16:50:39 +00:00
final Intent intent = new Intent ( mXmppConnectionService , XmppConnectionService . class ) ;
2020-04-10 13:19:56 +00:00
intent . setAction ( action ) ;
2020-04-07 16:50:39 +00:00
intent . setPackage ( mXmppConnectionService . getPackageName ( ) ) ;
intent . putExtra ( RtpSessionActivity . EXTRA_SESSION_ID , sessionId ) ;
return PendingIntent . getService ( mXmppConnectionService , requestCode , intent , PendingIntent . FLAG_UPDATE_CURRENT ) ;
}
2018-09-05 19:37:05 +00:00
private PendingIntent createSnoozeIntent ( Conversation conversation ) {
final Intent intent = new Intent ( mXmppConnectionService , XmppConnectionService . class ) ;
intent . setAction ( XmppConnectionService . ACTION_SNOOZE ) ;
intent . putExtra ( " uuid " , conversation . getUuid ( ) ) ;
intent . setPackage ( mXmppConnectionService . getPackageName ( ) ) ;
return PendingIntent . getService ( mXmppConnectionService , generateRequestCode ( conversation , 22 ) , intent , PendingIntent . FLAG_UPDATE_CURRENT ) ;
}
private PendingIntent createTryAgainIntent ( ) {
final Intent intent = new Intent ( mXmppConnectionService , XmppConnectionService . class ) ;
intent . setAction ( XmppConnectionService . ACTION_TRY_AGAIN ) ;
return PendingIntent . getService ( mXmppConnectionService , 45 , intent , 0 ) ;
}
private PendingIntent createDismissErrorIntent ( ) {
final Intent intent = new Intent ( mXmppConnectionService , XmppConnectionService . class ) ;
intent . setAction ( XmppConnectionService . ACTION_DISMISS_ERROR_NOTIFICATIONS ) ;
return PendingIntent . getService ( mXmppConnectionService , 69 , intent , 0 ) ;
}
private boolean wasHighlightedOrPrivate ( final Message message ) {
if ( message . getConversation ( ) instanceof Conversation ) {
Conversation conversation = ( Conversation ) message . getConversation ( ) ;
final String nick = conversation . getMucOptions ( ) . getActualNick ( ) ;
final Pattern highlight = generateNickHighlightPattern ( nick ) ;
if ( message . getBody ( ) = = null | | nick = = null ) {
return false ;
}
final Matcher m = highlight . matcher ( message . getBody ( ) ) ;
2019-04-27 09:46:43 +00:00
return ( m . find ( ) | | message . isPrivateMessage ( ) ) ;
2018-09-05 19:37:05 +00:00
} else {
return false ;
}
}
public void setOpenConversation ( final Conversation conversation ) {
this . mOpenConversation = conversation ;
}
public void setIsInForeground ( final boolean foreground ) {
this . mIsInForeground = foreground ;
}
private int getPixel ( final int dp ) {
final DisplayMetrics metrics = mXmppConnectionService . getResources ( )
. getDisplayMetrics ( ) ;
return ( ( int ) ( dp * metrics . density ) ) ;
}
private void markLastNotification ( ) {
this . mLastNotification = SystemClock . elapsedRealtime ( ) ;
}
private boolean inMiniGracePeriod ( final Account account ) {
final int miniGrace = account . getStatus ( ) = = Account . State . ONLINE ? Config . MINI_GRACE_PERIOD
: Config . MINI_GRACE_PERIOD * 2 ;
return SystemClock . elapsedRealtime ( ) < ( this . mLastNotification + miniGrace ) ;
}
2018-11-07 12:42:01 +00:00
Notification createForegroundNotification ( ) {
2018-09-05 19:37:05 +00:00
final Notification . Builder mBuilder = new Notification . Builder ( mXmppConnectionService ) ;
2018-11-07 12:42:01 +00:00
mBuilder . setContentTitle ( mXmppConnectionService . getString ( R . string . app_name ) ) ;
2019-01-23 16:07:14 +00:00
final List < Account > accounts = mXmppConnectionService . getAccounts ( ) ;
int enabled = 0 ;
int connected = 0 ;
if ( accounts ! = null ) {
for ( Account account : accounts ) {
if ( account . isOnlineAndConnected ( ) ) {
connected + + ;
enabled + + ;
} else if ( account . isEnabled ( ) ) {
enabled + + ;
2018-09-05 19:37:05 +00:00
}
}
}
2019-01-23 16:07:14 +00:00
mBuilder . setContentText ( mXmppConnectionService . getString ( R . string . connected_accounts , connected , enabled ) ) ;
2019-10-23 20:33:51 +00:00
final PendingIntent openIntent = createOpenConversationsIntent ( ) ;
if ( openIntent ! = null ) {
mBuilder . setContentIntent ( openIntent ) ;
}
2018-09-05 19:37:05 +00:00
mBuilder . setWhen ( 0 ) ;
2018-10-11 00:44:17 +00:00
mBuilder . setPriority ( Notification . PRIORITY_MIN ) ;
2019-01-23 16:07:14 +00:00
mBuilder . setSmallIcon ( connected > 0 ? R . drawable . ic_link_white_24dp : R . drawable . ic_link_off_white_24dp ) ;
2018-09-05 19:37:05 +00:00
2018-09-18 09:33:18 +00:00
if ( Compatibility . runsTwentySix ( ) ) {
2018-09-05 19:37:05 +00:00
mBuilder . setChannelId ( " foreground " ) ;
}
return mBuilder . build ( ) ;
}
private PendingIntent createOpenConversationsIntent ( ) {
2019-10-23 20:33:51 +00:00
try {
return PendingIntent . getActivity ( mXmppConnectionService , 0 , new Intent ( mXmppConnectionService , ConversationsActivity . class ) , 0 ) ;
} catch ( RuntimeException e ) {
return null ;
}
2018-09-05 19:37:05 +00:00
}
2018-11-11 08:54:52 +00:00
void updateErrorNotification ( ) {
2018-09-05 19:37:05 +00:00
if ( Config . SUPPRESS_ERROR_NOTIFICATION ) {
cancel ( ERROR_NOTIFICATION_ID ) ;
return ;
}
2018-10-29 11:00:25 +00:00
final boolean showAllErrors = QuickConversationsService . isConversations ( ) ;
2018-09-05 19:37:05 +00:00
final List < Account > errors = new ArrayList < > ( ) ;
for ( final Account account : mXmppConnectionService . getAccounts ( ) ) {
2018-10-29 11:00:25 +00:00
if ( account . hasErrorStatus ( ) & & account . showErrorNotification ( ) & & ( showAllErrors | | account . getLastErrorStatus ( ) = = Account . State . UNAUTHORIZED ) ) {
2018-09-05 19:37:05 +00:00
errors . add ( account ) ;
}
}
2018-11-11 08:54:52 +00:00
if ( mXmppConnectionService . foregroundNotificationNeedsUpdatingWhenErrorStateChanges ( ) ) {
2018-09-05 19:37:05 +00:00
notify ( FOREGROUND_NOTIFICATION_ID , createForegroundNotification ( ) ) ;
}
final Notification . Builder mBuilder = new Notification . Builder ( mXmppConnectionService ) ;
if ( errors . size ( ) = = 0 ) {
cancel ( ERROR_NOTIFICATION_ID ) ;
return ;
} else if ( errors . size ( ) = = 1 ) {
mBuilder . setContentTitle ( mXmppConnectionService . getString ( R . string . problem_connecting_to_account ) ) ;
mBuilder . setContentText ( errors . get ( 0 ) . getJid ( ) . asBareJid ( ) . toString ( ) ) ;
} else {
mBuilder . setContentTitle ( mXmppConnectionService . getString ( R . string . problem_connecting_to_accounts ) ) ;
mBuilder . setContentText ( mXmppConnectionService . getString ( R . string . touch_to_fix ) ) ;
}
mBuilder . addAction ( R . drawable . ic_autorenew_white_24dp ,
mXmppConnectionService . getString ( R . string . try_again ) ,
createTryAgainIntent ( ) ) ;
mBuilder . setDeleteIntent ( createDismissErrorIntent ( ) ) ;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . LOLLIPOP ) {
2019-09-11 13:42:43 +00:00
mBuilder . setVisibility ( Notification . VISIBILITY_PRIVATE ) ;
2018-09-05 19:37:05 +00:00
mBuilder . setSmallIcon ( R . drawable . ic_warning_white_24dp ) ;
} else {
mBuilder . setSmallIcon ( R . drawable . ic_stat_alert_warning ) ;
}
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . KITKAT_WATCH ) {
mBuilder . setLocalOnly ( true ) ;
}
mBuilder . setPriority ( Notification . PRIORITY_LOW ) ;
2018-10-09 17:35:33 +00:00
final Intent intent ;
if ( AccountUtils . MANAGE_ACCOUNT_ACTIVITY ! = null ) {
intent = new Intent ( mXmppConnectionService , AccountUtils . MANAGE_ACCOUNT_ACTIVITY ) ;
} else {
intent = new Intent ( mXmppConnectionService , EditAccountActivity . class ) ;
intent . putExtra ( " jid " , errors . get ( 0 ) . getJid ( ) . asBareJid ( ) . toEscapedString ( ) ) ;
2018-10-25 20:06:41 +00:00
intent . putExtra ( EditAccountActivity . EXTRA_OPENED_FROM_NOTIFICATION , true ) ;
2018-10-09 17:35:33 +00:00
}
2018-10-25 20:06:41 +00:00
mBuilder . setContentIntent ( PendingIntent . getActivity ( mXmppConnectionService , 145 , intent , PendingIntent . FLAG_UPDATE_CURRENT ) ) ;
2018-09-18 09:33:18 +00:00
if ( Compatibility . runsTwentySix ( ) ) {
2018-09-05 19:37:05 +00:00
mBuilder . setChannelId ( " error " ) ;
}
notify ( ERROR_NOTIFICATION_ID , mBuilder . build ( ) ) ;
}
2018-11-11 08:54:52 +00:00
void updateFileAddingNotification ( int current , Message message ) {
2018-09-05 19:37:05 +00:00
Notification . Builder mBuilder = new Notification . Builder ( mXmppConnectionService ) ;
mBuilder . setContentTitle ( mXmppConnectionService . getString ( R . string . transcoding_video ) ) ;
mBuilder . setProgress ( 100 , current , false ) ;
mBuilder . setSmallIcon ( R . drawable . ic_hourglass_empty_white_24dp ) ;
mBuilder . setContentIntent ( createContentIntent ( message . getConversation ( ) ) ) ;
2018-09-06 20:05:15 +00:00
mBuilder . setOngoing ( true ) ;
2018-09-18 09:33:18 +00:00
if ( Compatibility . runsTwentySix ( ) ) {
2018-09-06 13:37:31 +00:00
mBuilder . setChannelId ( " compression " ) ;
2018-09-05 19:37:05 +00:00
}
Notification notification = mBuilder . build ( ) ;
notify ( FOREGROUND_NOTIFICATION_ID , notification ) ;
}
private void notify ( String tag , int id , Notification notification ) {
final NotificationManagerCompat notificationManager = NotificationManagerCompat . from ( mXmppConnectionService ) ;
try {
notificationManager . notify ( tag , id , notification ) ;
} catch ( RuntimeException e ) {
Log . d ( Config . LOGTAG , " unable to make notification " , e ) ;
}
}
2019-02-16 10:58:16 +00:00
public void notify ( int id , Notification notification ) {
2018-09-05 19:37:05 +00:00
final NotificationManagerCompat notificationManager = NotificationManagerCompat . from ( mXmppConnectionService ) ;
try {
notificationManager . notify ( id , notification ) ;
} catch ( RuntimeException e ) {
Log . d ( Config . LOGTAG , " unable to make notification " , e ) ;
}
}
2020-04-11 17:05:07 +00:00
public void cancel ( int id ) {
2018-09-05 19:37:05 +00:00
final NotificationManagerCompat notificationManager = NotificationManagerCompat . from ( mXmppConnectionService ) ;
try {
notificationManager . cancel ( id ) ;
} catch ( RuntimeException e ) {
Log . d ( Config . LOGTAG , " unable to cancel notification " , e ) ;
}
}
private void cancel ( String tag , int id ) {
final NotificationManagerCompat notificationManager = NotificationManagerCompat . from ( mXmppConnectionService ) ;
try {
notificationManager . cancel ( tag , id ) ;
} catch ( RuntimeException e ) {
Log . d ( Config . LOGTAG , " unable to cancel notification " , e ) ;
}
}
2014-09-28 13:21:56 +00:00
}