Select Git revision
NotificationServiceImpl.java
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
NotificationServiceImpl.java 45.02 KiB
/*
* Copyright (C) 2004-2019 Savoir-faire Linux Inc.
*
* Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package cx.ring.services;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.media.AudioAttributes;
import android.media.RingtoneManager;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.CarExtender.UnreadConversation;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.Person;
import androidx.core.app.RemoteInput;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.graphics.drawable.IconCompat;
import com.bumptech.glide.Glide;
import java.io.File;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Inject;
import cx.ring.R;
import cx.ring.client.ConversationActivity;
import cx.ring.client.HomeActivity;
import cx.ring.contactrequests.ContactRequestsFragment;
import cx.ring.contacts.AvatarFactory;
import cx.ring.fragments.ConversationFragment;
import cx.ring.model.Account;
import cx.ring.model.CallContact;
import cx.ring.model.Conference;
import cx.ring.model.Conversation;
import cx.ring.model.Interaction;
import cx.ring.model.Interaction.InteractionStatus;
import cx.ring.model.DataTransfer;
import cx.ring.model.SipCall;
import cx.ring.model.TextMessage;
import cx.ring.model.Uri;
import cx.ring.service.CallNotificationService;
import cx.ring.service.DRingService;
import cx.ring.tv.call.TVCallActivity;
import cx.ring.utils.ConversationPath;
import cx.ring.utils.DeviceUtils;
import cx.ring.utils.FileUtils;
import cx.ring.utils.ResourceMapper;
import cx.ring.utils.Tuple;
public class NotificationServiceImpl implements NotificationService {
private static final String TAG = NotificationServiceImpl.class.getSimpleName();
private static final String NOTIF_MSG = "MESSAGE";
private static final String NOTIF_TRUST_REQUEST = "TRUST REQUEST";
private static final String NOTIF_FILE_TRANSFER = "FILE_TRANSFER";
private static final String NOTIF_MISSED_CALL = "MISSED_CALL";
private static final String NOTIF_CHANNEL_CALL_IN_PROGRESS = "current_call";
private static final String NOTIF_CHANNEL_MISSED_CALL = "missed_calls";
private static final String NOTIF_CHANNEL_INCOMING_CALL = "incoming_call";
private static final String NOTIF_CHANNEL_MESSAGE = "messages";
private static final String NOTIF_CHANNEL_REQUEST = "requests";
private static final String NOTIF_CHANNEL_FILE_TRANSFER = "file_transfer";
public static final String NOTIF_CHANNEL_SYNC = "sync";
private static final String NOTIF_CHANNEL_SERVICE = "service";
// old channel codes that were replaced or split
private static final String NOTIF_CHANNEL_CALL_LEGACY = "call";
private static final String NOTIF_CHANNEL_MISSED_CALL_LEGACY = "missed_call";
private static final String NOTIF_CALL_GROUP = "calls";
private static final int NOTIF_CALL_ID = 1001;
private final SparseArray<NotificationCompat.Builder> mNotificationBuilders = new SparseArray<>();
@Inject
protected Context mContext;
@Inject
protected AccountService mAccountService;
@Inject
protected ContactService mContactService;
@Inject
protected PreferencesService mPreferencesService;
@Inject
protected HistoryService mHistoryService;
@Inject
protected DeviceRuntimeService mDeviceRuntimeService;
private NotificationManagerCompat notificationManager;
private final Random random = new Random();
private int avatarSize;
private LinkedHashMap<Integer, Conference> currentCalls = new LinkedHashMap<>();
private ConcurrentHashMap<Integer, Notification> dataTransferNotifications = new ConcurrentHashMap<>();
@SuppressLint("CheckResult")
public void initHelper() {
if (notificationManager == null) {
notificationManager = NotificationManagerCompat.from(mContext);
}
avatarSize = (int) (mContext.getResources().getDisplayMetrics().density * AvatarFactory.SIZE_NOTIF);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
registerNotificationChannels();
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void registerNotificationChannels() {
NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null)
return;
notificationManager.deleteNotificationChannel(NOTIF_CHANNEL_MISSED_CALL_LEGACY);
notificationManager.deleteNotificationChannel(NOTIF_CHANNEL_CALL_LEGACY);
// Setting up groups
notificationManager.createNotificationChannelGroup(new NotificationChannelGroup(NOTIF_CALL_GROUP, mContext.getString(R.string.notif_group_calls)));
// Missed calls channel
NotificationChannel missedCallsChannel = new NotificationChannel(NOTIF_CHANNEL_MISSED_CALL, mContext.getString(R.string.notif_channel_missed_calls), NotificationManager.IMPORTANCE_DEFAULT);
missedCallsChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
missedCallsChannel.setSound(null, null);
missedCallsChannel.enableVibration(false);
missedCallsChannel.setGroup(NOTIF_CALL_GROUP);
notificationManager.createNotificationChannel(missedCallsChannel);
// Incoming call channel
NotificationChannel incomingCallChannel = new NotificationChannel(NOTIF_CHANNEL_INCOMING_CALL, mContext.getString(R.string.notif_channel_incoming_calls), NotificationManager.IMPORTANCE_HIGH);
incomingCallChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
incomingCallChannel.setGroup(NOTIF_CALL_GROUP);
incomingCallChannel.setSound(null, null);
incomingCallChannel.enableVibration(false);
notificationManager.createNotificationChannel(incomingCallChannel);
// Call in progress channel
NotificationChannel callInProgressChannel = new NotificationChannel(NOTIF_CHANNEL_CALL_IN_PROGRESS, mContext.getString(R.string.notif_channel_call_in_progress), NotificationManager.IMPORTANCE_DEFAULT);
callInProgressChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
callInProgressChannel.setSound(null, null);
callInProgressChannel.enableVibration(false);
callInProgressChannel.setGroup(NOTIF_CALL_GROUP);
notificationManager.createNotificationChannel(callInProgressChannel);
// Text messages channel
AudioAttributes soundAttributes = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
.build();
NotificationChannel messageChannel = new NotificationChannel(NOTIF_CHANNEL_MESSAGE, mContext.getString(R.string.notif_channel_messages), NotificationManager.IMPORTANCE_HIGH);
messageChannel.enableVibration(true);
messageChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
messageChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), soundAttributes);
notificationManager.createNotificationChannel(messageChannel);
// Contact requests
NotificationChannel requestsChannel = new NotificationChannel(NOTIF_CHANNEL_REQUEST, mContext.getString(R.string.notif_channel_requests), NotificationManager.IMPORTANCE_DEFAULT);
requestsChannel.enableVibration(true);
requestsChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
requestsChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), soundAttributes);
notificationManager.createNotificationChannel(requestsChannel);
// File transfer requests
NotificationChannel fileTransferChannel = new NotificationChannel(NOTIF_CHANNEL_FILE_TRANSFER, mContext.getString(R.string.notif_channel_file_transfer), NotificationManager.IMPORTANCE_DEFAULT);
fileTransferChannel.enableVibration(true);
fileTransferChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
fileTransferChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), soundAttributes);
notificationManager.createNotificationChannel(fileTransferChannel);
// File transfer requests
NotificationChannel syncChannel = new NotificationChannel(NOTIF_CHANNEL_SYNC, mContext.getString(R.string.notif_channel_sync), NotificationManager.IMPORTANCE_DEFAULT);
syncChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
syncChannel.enableLights(false);
syncChannel.enableVibration(false);
syncChannel.setShowBadge(false);
syncChannel.setSound(null, null);
notificationManager.createNotificationChannel(syncChannel);
// Background service channel
NotificationChannel backgroundChannel = new NotificationChannel(NOTIF_CHANNEL_SERVICE, mContext.getString(R.string.notif_channel_background_service), NotificationManager.IMPORTANCE_LOW);
backgroundChannel.setDescription(mContext.getString(R.string.notif_channel_background_service_descr));
backgroundChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
backgroundChannel.enableLights(false);
backgroundChannel.enableVibration(false);
backgroundChannel.setShowBadge(false);
notificationManager.createNotificationChannel(backgroundChannel);
}
/**
* Starts the call activity directly for Android TV
*
* @param callId the call ID
*/
private void startCallActivity(String callId) {
mContext.startActivity(new Intent(Intent.ACTION_VIEW)
.putExtra(KEY_CALL_ID, callId)
.setClass(mContext.getApplicationContext(), TVCallActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
}
@Override
public Object showCallNotification(int callId) {
Conference mConference = currentCalls.get(callId);
if (mConference == null || mConference.getParticipants().isEmpty()) {
return null;
}
SipCall call = mConference.getParticipants().get(0);
notificationManager.cancel(NOTIF_CALL_ID);
PendingIntent gotoIntent = PendingIntent.getService(mContext,
random.nextInt(),
new Intent(DRingService.ACTION_CALL_VIEW)
.setClass(mContext, DRingService.class)
.putExtra(KEY_CALL_ID, call.getDaemonIdString()), 0);
CallContact contact = call.getContact();
NotificationCompat.Builder messageNotificationBuilder;
if (mConference.isOnGoing()) {
messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_CALL_IN_PROGRESS);
messageNotificationBuilder.setContentTitle(mContext.getString(R.string.notif_current_call_title, contact.getDisplayName()))
.setContentText(mContext.getText(R.string.notif_current_call))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(gotoIntent)
.setSound(null)
.setVibrate(null)
.setColorized(true)
.setColor(mContext.getResources().getColor(R.color.color_primary_light))
.addAction(R.drawable.baseline_call_end_24, mContext.getText(R.string.action_call_hangup),
PendingIntent.getService(mContext, random.nextInt(),
new Intent(DRingService.ACTION_CALL_END)
.setClass(mContext, DRingService.class)
.putExtra(KEY_CALL_ID, call.getDaemonIdString()),
PendingIntent.FLAG_ONE_SHOT));
} else if (mConference.isRinging()) {
if (mConference.isIncoming()) {
messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_INCOMING_CALL);
messageNotificationBuilder.setContentTitle(mContext.getString(R.string.notif_incoming_call_title, contact.getDisplayName()))
.setPriority(NotificationCompat.PRIORITY_MAX)
.setContentText(mContext.getText(R.string.notif_incoming_call))
.setContentIntent(gotoIntent)
.setSound(null)
.setVibrate(null)
.setFullScreenIntent(gotoIntent, true)
.addAction(R.drawable.baseline_call_end_24, mContext.getText(R.string.action_call_decline),
PendingIntent.getService(mContext, random.nextInt(),
new Intent(DRingService.ACTION_CALL_REFUSE)
.setClass(mContext, DRingService.class)
.putExtra(KEY_CALL_ID, call.getDaemonIdString()),
PendingIntent.FLAG_ONE_SHOT))
.addAction(R.drawable.baseline_call_24, mContext.getText(R.string.action_call_accept),
PendingIntent.getService(mContext, random.nextInt(),
new Intent(DRingService.ACTION_CALL_ACCEPT)
.setClass(mContext, DRingService.class)
.putExtra(KEY_CALL_ID, call.getDaemonIdString()),
PendingIntent.FLAG_ONE_SHOT));
} else {
messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_CALL_IN_PROGRESS);
messageNotificationBuilder.setContentTitle(mContext.getString(R.string.notif_outgoing_call_title, contact.getDisplayName()))
.setContentText(mContext.getText(R.string.notif_outgoing_call))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(gotoIntent)
.setSound(null)
.setVibrate(null)
.setColorized(true)
.setColor(mContext.getResources().getColor(R.color.color_primary_light))
.addAction(R.drawable.baseline_call_end_24, mContext.getText(R.string.action_call_hangup),
PendingIntent.getService(mContext, random.nextInt(),
new Intent(DRingService.ACTION_CALL_END)
.setClass(mContext, DRingService.class)
.putExtra(KEY_CALL_ID, call.getDaemonIdString()),
PendingIntent.FLAG_ONE_SHOT));
}
} else {
handleCallNotification(mConference, true);
return null;
}
messageNotificationBuilder.setOngoing(true)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setSmallIcon(R.drawable.ic_ring_logo_white);
setContactPicture(contact, messageNotificationBuilder);
return messageNotificationBuilder.build();
}
/**
* Updates a notification
*
* @param notification a built notification object
* @param notificationId the notification's id
*/
@Override
public void updateNotification(Object notification, int notificationId) {
if(notification != null)
notificationManager.notify(notificationId, (Notification) notification);
}
/**
* Starts a service (data transfer or call)
*
* @param id the notification id
*/
private void startForegroundService(int id, Class serviceClass) {
Intent serviceIntent = new Intent(mContext, serviceClass);
serviceIntent.putExtra(KEY_NOTIFICATION_ID, id);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
mContext.startForegroundService(serviceIntent);
else
mContext.startService(serviceIntent);
}
/**
* Handles the creation and destruction of services associated with calls as well as displaying notifications.
*
* @param conference the conference object for the notification
* @param remove true if it should be removed from current calls
*/
@Override
public void handleCallNotification(Conference conference, boolean remove) {
if (DeviceUtils.isTv(mContext)) {
if (!remove)
startCallActivity(conference.getId());
return;
}
int id = conference.getId().hashCode();
currentCalls.remove(id);
if (!remove) {
currentCalls.put(id, conference);
startForegroundService(id, CallNotificationService.class);
} else if (currentCalls.isEmpty())
mContext.stopService(new Intent(mContext, CallNotificationService.class));
// this is a temporary solution until we have direct support for concurrent calls and the call state will exclusively update notifications
else {
int key = -1;
Iterator<Integer> iterator = (currentCalls.keySet().iterator());
while (iterator.hasNext()) {
key = iterator.next();
}
updateNotification(showCallNotification(key), NOTIF_CALL_ID);
}
}
@Override
public void onConnectionUpdate(Boolean b) {
Log.i(TAG, "onConnectionUpdate " + b);
if (b) {
Intent serviceIntent = new Intent(SyncService.ACTION_START).setClass(mContext, SyncService.class);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
mContext.startForegroundService(serviceIntent);
else
mContext.startService(serviceIntent);
} catch (IllegalStateException e) {
Log.e(TAG, "Error starting service", e);
}
} else {
try {
mContext.startService(new Intent(SyncService.ACTION_STOP).setClass(mContext, SyncService.class));
} catch (IllegalStateException ignored) {
}
}
}
/**
* Handles the creation and destruction of services associated with transfers as well as displaying notifications.
*
* @param transfer the data transfer object
* @param contact the contact to whom the data transfer is being sent
* @param remove true if it should be removed from current calls
*/
@Override
public void handleDataTransferNotification(DataTransfer transfer, CallContact contact, boolean remove) {
Log.d(TAG, "handleDataTransferNotification, a data transfer event is in progress");
if (DeviceUtils.isTv(mContext)) {
return;
}
if (!remove) {
showFileTransferNotification(transfer, contact);
} else {
removeTransferNotification(transfer.getDaemonId());
}
}
/**
* Cancels a data transfer notification and removes it from the list of notifications
*
* @param transferId the transfer id which is required to generate the notification id
*/
@Override
public void removeTransferNotification(long transferId) {
int id = getFileTransferNotificationId(transferId);
dataTransferNotifications.remove(id);
cancelFileNotification(id, false);
if (dataTransferNotifications.isEmpty())
mContext.stopService(new Intent(mContext, DataTransferService.class));
else {
startForegroundService(dataTransferNotifications.keySet().iterator().next(), DataTransferService.class);
}
}
/**
* @param notificationId the notification id
* @return the notification object for a data transfer notification
*/
@Override
public Notification getDataTransferNotification(int notificationId) {
return dataTransferNotifications.get(notificationId);
}
@Override
public void showTextNotification(String accountId, Conversation conversation) {
TreeMap<Long, TextMessage> texts = conversation.getUnreadTextMessages();
CallContact contact = conversation.getContact();
if (texts.isEmpty() || conversation.isVisible()) {
cancelTextNotification(contact.getPrimaryUri());
return;
}
if (texts.lastEntry().getValue().isNotified()) {
return;
}
Log.w(TAG, "showTextNotification " + accountId + " " + contact.getPrimaryNumber());
mContactService.getLoadedContact(accountId, contact)
.subscribe(c -> textNotification(accountId, texts, c),
e -> Log.w(TAG, "Can't load contact", e));
}
private void textNotification(String accountId, TreeMap<Long, TextMessage> texts, CallContact contact) {
Uri contactUri = contact.getPrimaryUri();
String contactId = contactUri.getUri();
android.net.Uri path = ConversationPath.toUri(accountId, contactId);
String contactName = contact.getDisplayName();
if (TextUtils.isEmpty(contactName) || texts.isEmpty())
return;
Bitmap contactPicture = getContactPicture(contact);
TextMessage last = texts.lastEntry().getValue();
Intent intentConversation = new Intent(DRingService.ACTION_CONV_ACCEPT, path, mContext, DRingService.class);
Intent intentDelete = new Intent(DRingService.ACTION_CONV_DISMISS, path, mContext, DRingService.class);
NotificationCompat.Builder messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_MESSAGE)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setPriority(Notification.PRIORITY_HIGH)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_ring_logo_white)
.setContentTitle(contactName)
.setContentText(last.getBody())
.setWhen(last.getTimestamp())
.setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
.setDeleteIntent(PendingIntent.getService(mContext, random.nextInt(), intentDelete, 0))
.setAutoCancel(true)
.setColor(ResourcesCompat.getColor(mContext.getResources(), R.color.color_primary_dark, null));
if (contactPicture != null) {
messageNotificationBuilder.setLargeIcon(contactPicture);
Intent intentBubble = new Intent(Intent.ACTION_VIEW, path, mContext, ConversationActivity.class);
messageNotificationBuilder.setBubbleMetadata(new NotificationCompat.BubbleMetadata.Builder()
.setDesiredHeight(600)
.setIcon(IconCompat.createWithAdaptiveBitmap(contactPicture))
.setIntent(PendingIntent.getActivity(mContext, 0, intentBubble, 0))
.build());
}
UnreadConversation.Builder unreadConvBuilder = new UnreadConversation.Builder(contactName)
.setLatestTimestamp(last.getTimestamp());
if (texts.size() == 1) {
last.setNotified(true);
messageNotificationBuilder.setStyle(null);
unreadConvBuilder.addMessage(last.getBody());
} else {
Account account = mAccountService.getAccount(accountId);
Tuple<String, Object> profile = account == null ? null : VCardServiceImpl.loadProfile(account).blockingGet();
Bitmap myPic = account == null ? null : getContactPicture(account);
Person userPerson = new Person.Builder()
.setKey(accountId)
.setName(profile == null || TextUtils.isEmpty(profile.first) ? "You" : profile.first)
.setIcon(myPic == null ? null : IconCompat.createWithBitmap(myPic))
.build();
Person contactPerson = new Person.Builder()
.setKey(contactId)
.setName(contactName)
.setIcon(contactPicture == null ? null : IconCompat.createWithBitmap(contactPicture))
.build();
NotificationCompat.MessagingStyle history = new NotificationCompat.MessagingStyle(userPerson);
for (TextMessage textMessage : texts.values()) {
history.addMessage(new NotificationCompat.MessagingStyle.Message(
textMessage.getBody(),
textMessage.getTimestamp(),
textMessage.isIncoming() ? contactPerson : null));
unreadConvBuilder.addMessage(textMessage.getBody());
}
messageNotificationBuilder.setStyle(history);
}
int notificationId = getTextNotificationId(contactUri);
int replyId = notificationId + 1;
int markAsReadId = notificationId + 2;
CharSequence replyLabel = mContext.getText(R.string.notif_reply);
RemoteInput remoteInput = new RemoteInput.Builder(DRingService.KEY_TEXT_REPLY)
.setLabel(replyLabel)
.build();
PendingIntent replyPendingIntent = PendingIntent.getService(mContext, replyId,
new Intent(DRingService.ACTION_CONV_REPLY_INLINE, path, mContext, DRingService.class),
PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent readPendingIntent = PendingIntent.getService(mContext, markAsReadId,
new Intent(DRingService.ACTION_CONV_READ, path, mContext, DRingService.class), 0);
messageNotificationBuilder
.addAction(new NotificationCompat.Action.Builder(R.drawable.baseline_reply_24, replyLabel, replyPendingIntent)
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
.addRemoteInput(remoteInput)
.build())
.addAction(new NotificationCompat.Action.Builder(0,
mContext.getString(R.string.notif_mark_as_read),
readPendingIntent)
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
.setShowsUserInterface(false)
.build())
.extend(new NotificationCompat.CarExtender()
.setUnreadConversation(unreadConvBuilder
.setReadPendingIntent(readPendingIntent)
.setReplyAction(replyPendingIntent, remoteInput)
.build()));
notificationManager.notify(notificationId, messageNotificationBuilder.build());
mNotificationBuilders.put(notificationId, messageNotificationBuilder);
}
private NotificationCompat.Builder getRequestNotificationBuilder(String accountId) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_REQUEST)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_ring_logo_white)
.setCategory(NotificationCompat.CATEGORY_SOCIAL)
.setContentTitle(mContext.getString(R.string.contact_request_title));
Intent intentOpenTrustRequestFragment = new Intent(HomeActivity.ACTION_PRESENT_TRUST_REQUEST_FRAGMENT)
.setClass(mContext, HomeActivity.class)
.putExtra(ContactRequestsFragment.ACCOUNT_ID, accountId);
builder.setContentIntent(PendingIntent.getActivity(mContext,
random.nextInt(), intentOpenTrustRequestFragment, PendingIntent.FLAG_ONE_SHOT));
builder.setColor(ResourcesCompat.getColor(mContext.getResources(),
R.color.color_primary_dark, null));
return builder;
}
@Override
public void showIncomingTrustRequestNotification(final Account account) {
int notificationId = getIncomingTrustNotificationId(account.getAccountID());
Set<String> notifiedRequests = mPreferencesService.loadRequestsPreferences(account.getAccountID());
Collection<Conversation> requests = account.getPending();
if (requests.isEmpty())
return;
if (requests.size() == 1) {
Conversation request = requests.iterator().next();
CallContact contact = request.getContact();
String contactKey = contact.getPrimaryUri().getRawRingId();
if (notifiedRequests.contains(contactKey)) {
return;
}
mContactService.getLoadedContact(account.getAccountID(), contact).subscribe(c -> {
NotificationCompat.Builder builder = getRequestNotificationBuilder(account.getAccountID());
mPreferencesService.saveRequestPreferences(account.getAccountID(), contactKey);
Bundle info = new Bundle();
info.putString(TRUST_REQUEST_NOTIFICATION_ACCOUNT_ID, account.getAccountID());
info.putString(TRUST_REQUEST_NOTIFICATION_FROM, c.getPrimaryNumber());
builder.setContentText(c.getRingUsername())
.addAction(R.drawable.baseline_person_add_24, mContext.getText(R.string.accept),
PendingIntent.getService(mContext, random.nextInt(),
new Intent(DRingService.ACTION_TRUST_REQUEST_ACCEPT)
.setClass(mContext, DRingService.class)
.putExtras(info),
PendingIntent.FLAG_ONE_SHOT))
.addAction(R.drawable.baseline_delete_24, mContext.getText(R.string.refuse),
PendingIntent.getService(mContext, random.nextInt(),
new Intent(DRingService.ACTION_TRUST_REQUEST_REFUSE)
.setClass(mContext, DRingService.class)
.putExtras(info),
PendingIntent.FLAG_ONE_SHOT))
.addAction(R.drawable.baseline_block_24, mContext.getText(R.string.block),
PendingIntent.getService(mContext, random.nextInt(),
new Intent(DRingService.ACTION_TRUST_REQUEST_BLOCK)
.setClass(mContext, DRingService.class)
.putExtras(info),
PendingIntent.FLAG_ONE_SHOT));
setContactPicture(c, builder);
notificationManager.notify(notificationId, builder.build());
}, e -> Log.w(TAG, "error showing notification", e));
} else {
NotificationCompat.Builder builder = getRequestNotificationBuilder(account.getAccountID());
boolean newRequest = false;
for (Conversation request : requests) {
CallContact contact = request.getContact();
String contactKey = contact.getPrimaryUri().getRawRingId();
if (!notifiedRequests.contains(contactKey)) {
newRequest = true;
mPreferencesService.saveRequestPreferences(account.getAccountID(), contactKey);
}
}
if (!newRequest)
return;
builder.setContentText(String.format(mContext.getString(R.string.contact_request_msg), Integer.toString(requests.size())));
builder.setLargeIcon(null);
notificationManager.notify(notificationId, builder.build());
}
}
@Override
public void showFileTransferNotification(DataTransfer info, CallContact contact) {
if (info == null) {
return;
}
InteractionStatus event = info.getStatus();
if (event == null) {
return;
}
long dataTransferId = info.getDaemonId();
int notificationId = getFileTransferNotificationId(dataTransferId);
android.net.Uri path = ConversationPath.toUri(info.getAccount(), new Uri(info.getConversation().getParticipant()));
Intent intentConversation = new Intent(DRingService.ACTION_CONV_ACCEPT, path, mContext, DRingService.class);
if (event.isOver()) {
removeTransferNotification(dataTransferId);
if (info.isOutgoing()) {
return;
}
NotificationCompat.Builder notif = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_FILE_TRANSFER)
.setSmallIcon(R.drawable.ic_ring_logo_white)
.setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
.setAutoCancel(true);
if (info.showPicture()) {
File filePath = mDeviceRuntimeService.getConversationPath(info.getPeerId(), info.getStoragePath());
Bitmap img;
try {
BitmapDrawable d = (BitmapDrawable) Glide.with(mContext)
.load(filePath)
.submit()
.get();
img = d.getBitmap();
notif.setContentTitle(mContext.getString(R.string.notif_incoming_picture, contact.getDisplayName()));
notif.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(img));
} catch (Exception e) {
Log.w(TAG, "Can't load image for notification", e);
return;
}
} else {
notif.setContentTitle(mContext.getString(R.string.notif_incoming_file_transfer_title, contact.getDisplayName()));
notif.setStyle(null);
}
setContactPicture(contact, notif);
notificationManager.notify(random.nextInt(), notif.build());
return;
}
NotificationCompat.Builder messageNotificationBuilder = mNotificationBuilders.get(notificationId);
if (messageNotificationBuilder == null) {
messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_FILE_TRANSFER);
}
boolean ongoing = event == InteractionStatus.TRANSFER_ONGOING || event == InteractionStatus.TRANSFER_ACCEPTED;
String titleMessage = mContext.getString(info.isOutgoing() ? R.string.notif_outgoing_file_transfer_title : R.string.notif_incoming_file_transfer_title, contact.getDisplayName());
messageNotificationBuilder.setContentTitle(titleMessage)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setAutoCancel(false)
.setOngoing(ongoing)
.setSmallIcon(R.drawable.ic_ring_logo_white)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.setOnlyAlertOnce(true)
.setContentText(event == Interaction.InteractionStatus.TRANSFER_ONGOING ?
Formatter.formatFileSize(mContext, info.getBytesProgress()) + " / " + Formatter.formatFileSize(mContext, info.getTotalSize()) :
info.getDisplayName() + ": " + ResourceMapper.getReadableFileTransferStatus(mContext, event))
.setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
.setColor(ResourcesCompat.getColor(mContext.getResources(), R.color.color_primary_dark, null));
setContactPicture(contact, messageNotificationBuilder);
if (event.isOver()) {
messageNotificationBuilder.setProgress(0, 0, false);
} else if (ongoing) {
messageNotificationBuilder.setProgress((int) info.getTotalSize(), (int) info.getBytesProgress(), false);
} else {
messageNotificationBuilder.setProgress(0, 0, true);
}
if (event == Interaction.InteractionStatus.TRANSFER_CREATED) {
messageNotificationBuilder.setDefaults(NotificationCompat.DEFAULT_VIBRATE);
mNotificationBuilders.put(notificationId, messageNotificationBuilder);
updateNotification(messageNotificationBuilder.build(), notificationId);
return;
} else {
messageNotificationBuilder.setDefaults(NotificationCompat.DEFAULT_LIGHTS);
}
messageNotificationBuilder.mActions.clear();
if (event == Interaction.InteractionStatus.TRANSFER_AWAITING_HOST) {
messageNotificationBuilder
.addAction(R.drawable.baseline_call_received_24, mContext.getText(R.string.accept),
PendingIntent.getService(mContext, random.nextInt(),
new Intent(DRingService.ACTION_FILE_ACCEPT)
.setClass(mContext, DRingService.class)
.putExtra(DRingService.KEY_TRANSFER_ID, dataTransferId),
PendingIntent.FLAG_ONE_SHOT))
.addAction(R.drawable.baseline_cancel_24, mContext.getText(R.string.refuse),
PendingIntent.getService(mContext, random.nextInt(),
new Intent(DRingService.ACTION_FILE_CANCEL)
.setClass(mContext, DRingService.class)
.putExtra(DRingService.KEY_TRANSFER_ID, dataTransferId),
PendingIntent.FLAG_ONE_SHOT));
mNotificationBuilders.put(notificationId, messageNotificationBuilder);
updateNotification(messageNotificationBuilder.build(), notificationId);
return;
} else if (!event.isOver()) {
messageNotificationBuilder
.addAction(R.drawable.baseline_cancel_24, mContext.getText(android.R.string.cancel),
PendingIntent.getService(mContext, random.nextInt(),
new Intent(DRingService.ACTION_FILE_CANCEL)
.setClass(mContext, DRingService.class)
.putExtra(DRingService.KEY_TRANSFER_ID, dataTransferId),
PendingIntent.FLAG_ONE_SHOT));
}
mNotificationBuilders.put(notificationId, messageNotificationBuilder);
dataTransferNotifications.remove(notificationId);
dataTransferNotifications.put(notificationId, messageNotificationBuilder.build());
startForegroundService(notificationId, DataTransferService.class);
}
@Override
public void showMissedCallNotification(SipCall call) {
final int notificationId = call.getDaemonIdString().hashCode();
NotificationCompat.Builder messageNotificationBuilder = mNotificationBuilders.get(notificationId);
if (messageNotificationBuilder == null) {
messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_MISSED_CALL);
}
android.net.Uri path = ConversationPath.toUri(call);
Intent intentConversation = new Intent(DRingService.ACTION_CONV_ACCEPT, path, mContext, DRingService.class);
messageNotificationBuilder.setContentTitle(mContext.getText(R.string.notif_missed_incoming_call))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.baseline_call_missed_24)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setOnlyAlertOnce(true)
.setAutoCancel(true)
.setContentText(call.getContact().getDisplayName())
.setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
.setColor(ResourcesCompat.getColor(mContext.getResources(), R.color.color_primary_dark, null));
setContactPicture(call.getContact(), messageNotificationBuilder);
notificationManager.notify(notificationId, messageNotificationBuilder.build());
}
@Override
public Object getServiceNotification() {
Intent intentHome = new Intent(Intent.ACTION_VIEW)
.setClass(mContext, HomeActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendIntent = PendingIntent.getActivity(mContext, 0, intentHome, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder messageNotificationBuilder = new NotificationCompat.Builder(mContext, NotificationServiceImpl.NOTIF_CHANNEL_SERVICE);
messageNotificationBuilder
.setContentTitle(mContext.getText(R.string.app_name))
.setContentText(mContext.getText(R.string.notif_background_service))
.setSmallIcon(R.drawable.ic_ring_logo_white)
.setContentIntent(pendIntent)
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setOngoing(true)
.setCategory(NotificationCompat.CATEGORY_SERVICE);
return messageNotificationBuilder.build();
}
@Override
public void cancelTextNotification(Uri contact) {
if (contact == null) {
return;
}
int notificationId = getTextNotificationId(contact);
notificationManager.cancel(notificationId);
mNotificationBuilders.remove(notificationId);
}
public void cancelTextNotification(String ringId) {
int notificationId = (NOTIF_MSG + ringId).hashCode();
notificationManager.cancel(notificationId);
mNotificationBuilders.remove(notificationId);
}
@Override
public void cancelTrustRequestNotification(String accountID) {
if (accountID == null) {
return;
}
int notificationId = getIncomingTrustNotificationId(accountID);
notificationManager.cancel(notificationId);
}
@Override
public void cancelCallNotification() {
notificationManager.cancel(NOTIF_CALL_ID);
mNotificationBuilders.remove(NOTIF_CALL_ID);
}
/**\
* Cancels a notification
* @param notificationId the notification ID
* @param isMigratingToService true if the notification is being updated to be a part of the foreground service
*/
@Override
public void cancelFileNotification(int notificationId, boolean isMigratingToService) {
notificationManager.cancel(notificationId);
if(!isMigratingToService)
mNotificationBuilders.remove(notificationId);
}
@Override
public void cancelAll() {
notificationManager.cancelAll();
mNotificationBuilders.clear();
}
private int getIncomingTrustNotificationId(String accountId) {
return (NOTIF_TRUST_REQUEST + accountId).hashCode();
}
private int getTextNotificationId(Uri contact) {
return (NOTIF_MSG + contact.toString()).hashCode();
}
private int getFileTransferNotificationId(long dataTransferId) {
return (NOTIF_FILE_TRANSFER + dataTransferId).hashCode();
}
private Bitmap getContactPicture(CallContact contact) {
try {
return AvatarFactory.getBitmapAvatar(mContext, contact, avatarSize).blockingGet();
} catch (Exception e) {
return null;
}
}
private Bitmap getContactPicture(Account account) {
return AvatarFactory.getBitmapAvatar(mContext, account, avatarSize).blockingGet();
}
private void setContactPicture(CallContact contact, NotificationCompat.Builder messageNotificationBuilder) {
Bitmap pic = getContactPicture(contact);
if (pic != null)
messageNotificationBuilder.setLargeIcon(pic);
}
}