mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 01:51:47 +00:00
Implemented notification support to notify the user about the newly received SMS/MMS when KDE Connect will be set as the default SMS app.
This commit is contained in:
parent
2758fba3d0
commit
0528de7fde
@ -95,6 +95,12 @@
|
||||
android:enabled="true"
|
||||
android:taskAffinity="com.klinker.android.messaging.MMS_SENT" />
|
||||
|
||||
<receiver
|
||||
android:name="org.kde.kdeconnect.Plugins.SMSPlugin.NotificationReplyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name="org.kde.kdeconnect.Plugins.SMSPlugin.HeadlessSmsSendService"
|
||||
android:exported="true"
|
||||
|
5
res/drawable/ic_baseline_sms_24.xml
Normal file
5
res/drawable/ic_baseline_sms_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#F67400"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM9,11L7,11L7,9h2v2zM13,11h-2L11,9h2v2zM17,11h-2L15,9h2v2z"/>
|
||||
</vector>
|
@ -308,6 +308,7 @@
|
||||
<string name="notification_channel_media_control">Media control</string>
|
||||
<string name="notification_channel_filetransfer">File transfer</string>
|
||||
<string name="notification_channel_high_priority">High priority</string>
|
||||
<string name="notification_channel_sms_mms">New Message</string>
|
||||
|
||||
<string name="mpris_stop">Stop the current player</string>
|
||||
<string name="copy_url_to_clipboard">Copy URL to clipboard</string>
|
||||
@ -374,6 +375,9 @@
|
||||
<string name="bigscreen_optional_permission_explanation">To share microphone input from your phone you need to give access to the phone\'s audio input</string>
|
||||
<string name="bigscreen_speech_extra_prompt">Speech</string>
|
||||
|
||||
<string name="message_reply_label">REPLY</string>
|
||||
<string name="mark_as_read_label">MARK AS READ</string>
|
||||
<string name="user_display_name">You</string>
|
||||
<string name="set_default_sms_app_title">Send MMS</string>
|
||||
<string name="set_group_message_as_mms_title">Send group MMS</string>
|
||||
<string name="set_group_message_as_mms" translatable="false">set_group_message_as_mms</string>
|
||||
|
@ -19,6 +19,7 @@ public class NotificationHelper {
|
||||
public final static String DEFAULT = "default";
|
||||
public final static String MEDIA_CONTROL = "media_control";
|
||||
public final static String FILETRANSFER = "filetransfer";
|
||||
public final static String SMS_MMS = "sms_mms";
|
||||
public final static String RECEIVENOTIFICATION = "receive";
|
||||
public final static String HIGHPRIORITY = "highpriority";
|
||||
}
|
||||
@ -83,6 +84,12 @@ public class NotificationHelper {
|
||||
NotificationManager.IMPORTANCE_DEFAULT)
|
||||
);
|
||||
|
||||
manager.createNotificationChannel(new NotificationChannel(
|
||||
Channels.SMS_MMS,
|
||||
context.getString(R.string.notification_channel_sms_mms),
|
||||
NotificationManager.IMPORTANCE_DEFAULT)
|
||||
);
|
||||
|
||||
NotificationChannel highPriority = new NotificationChannel(Channels.HIGHPRIORITY, context.getString(R.string.notification_channel_high_priority), NotificationManager.IMPORTANCE_HIGH);
|
||||
manager.createNotificationChannel(highPriority);
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ import org.kde.kdeconnect.Helpers.AppsHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
import org.kde.kdeconnect.Plugins.SMSPlugin.NotificationReplyReceiver;
|
||||
import org.kde.kdeconnect.UserInterface.MainActivity;
|
||||
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
||||
import org.kde.kdeconnect.UserInterface.StartActivityAlertDialogFragment;
|
||||
@ -203,7 +204,6 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
String packageName = statusBarNotification.getPackageName();
|
||||
String appName = AppsHelper.appNameLookup(context, packageName);
|
||||
|
||||
|
||||
if ("com.facebook.orca".equals(packageName) &&
|
||||
(statusBarNotification.getId() == 10012) &&
|
||||
"Messenger".equals(appName) &&
|
||||
@ -219,8 +219,18 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
}
|
||||
|
||||
if ("org.kde.kdeconnect_tp".equals(packageName)) {
|
||||
// Don't send our own notifications
|
||||
return;
|
||||
// Don't send our own notifications except notifications posted by SMSPlugin
|
||||
String groupKey = "";
|
||||
|
||||
// SMS Notifications on devices running API's lower than Lollipop are not supported
|
||||
// as groupKey's are not supported on API's older than Lollipop
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
groupKey = statusBarNotification.getGroupKey();
|
||||
}
|
||||
|
||||
if (!groupKey.contains(NotificationReplyReceiver.SMS_NOTIFICATION_GROUP_KEY)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NetworkPacket np = new NetworkPacket(PACKET_TYPE_NOTIFICATION);
|
||||
|
@ -24,17 +24,31 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.Person;
|
||||
import androidx.core.app.RemoteInput;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.klinker.android.send_message.Transaction;
|
||||
import com.klinker.android.send_message.Utils;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.TelephonyHelper;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect.Helpers.SMSHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Receiver for notifying user when a new MMS has been received by the device. By default it will
|
||||
* persist the message to the internal database and notification service to notify the users will be
|
||||
* implemented later.
|
||||
* persist the message to the internal database and it will also show a notification in the status bar.
|
||||
*/
|
||||
public class MmsReceivedReceiver extends com.klinker.android.send_message.MmsReceivedReceiver {
|
||||
|
||||
@ -47,11 +61,23 @@ public class MmsReceivedReceiver extends com.klinker.android.send_message.MmsRec
|
||||
// Notify messageUpdateReceiver about the arrival of the new MMS message
|
||||
Intent refreshIntent = new Intent(Transaction.REFRESH);
|
||||
context.sendBroadcast(refreshIntent);
|
||||
|
||||
// Fetch the latest message from the database
|
||||
SMSHelper.Message message = SMSHelper.getNewestMessage(context);
|
||||
|
||||
// Notify the user about the received mms message
|
||||
createMmsNotification(context, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Context context, String error) {
|
||||
Log.v("MmsReceived", "error: " + error);
|
||||
|
||||
// Fetch the latest message from the database
|
||||
SMSHelper.Message message = SMSHelper.getNewestMessage(context);
|
||||
|
||||
// Notify the user about the received mms message
|
||||
createMmsNotification(context, message);
|
||||
}
|
||||
|
||||
public void getPreferredApn(Context context, Intent intent) {
|
||||
@ -80,4 +106,72 @@ public class MmsReceivedReceiver extends com.klinker.android.send_message.MmsRec
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void createMmsNotification(Context context, SMSHelper.Message mmsMessage) {
|
||||
ArrayList<String> addressList = new ArrayList<>();
|
||||
for (SMSHelper.Address address : mmsMessage.addresses) {
|
||||
addressList.add(address.toString());
|
||||
}
|
||||
|
||||
Person sender = NotificationReplyReceiver.getMessageSender(context, addressList.get(0));
|
||||
|
||||
int notificationId;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
notificationId = (int) Utils.getOrCreateThreadId(context, new HashSet<>(addressList));
|
||||
} else {
|
||||
notificationId = (int) System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// Todo: When SMSHelper.Message class will be modified to contain thumbnail of the image or video attachment, add them here to display.
|
||||
|
||||
// Create pending intent for reply action through notification
|
||||
PendingIntent replyPendingIntent = NotificationReplyReceiver.createReplyPendingIntent(
|
||||
context,
|
||||
addressList,
|
||||
notificationId,
|
||||
true
|
||||
);
|
||||
|
||||
RemoteInput remoteReplyInput = new RemoteInput.Builder(NotificationReplyReceiver.KEY_TEXT_REPLY)
|
||||
.setLabel(context.getString(R.string.message_reply_label))
|
||||
.build();
|
||||
|
||||
NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(0, context.getString(R.string.message_reply_label), replyPendingIntent)
|
||||
.addRemoteInput(remoteReplyInput)
|
||||
.setAllowGeneratedReplies(true)
|
||||
.build();
|
||||
|
||||
// Create pending intent for marking the message as read in database through mark as read action
|
||||
PendingIntent markAsReadPendingIntent = NotificationReplyReceiver.createMarkAsReadPendingIntent(
|
||||
context,
|
||||
addressList,
|
||||
notificationId
|
||||
);
|
||||
|
||||
NotificationCompat.Action markAsReadAction = new NotificationCompat.Action.Builder(0, context.getString(R.string.mark_as_read_label), markAsReadPendingIntent)
|
||||
.build();
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationCompat.MessagingStyle messagingStyle = NotificationReplyReceiver.createMessagingStyle(
|
||||
context,
|
||||
notificationId,
|
||||
mmsMessage.body,
|
||||
TextUtils.join(",", addressList),
|
||||
mmsMessage.date,
|
||||
sender,
|
||||
notificationManager
|
||||
);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationReplyReceiver.CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_baseline_sms_24)
|
||||
.setColor(ContextCompat.getColor(context, R.color.primary))
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setStyle(messagingStyle)
|
||||
.setAutoCancel(true)
|
||||
.addAction(replyAction)
|
||||
.addAction(markAsReadAction)
|
||||
.setGroup(NotificationReplyReceiver.SMS_NOTIFICATION_GROUP_KEY);
|
||||
|
||||
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,10 @@ import android.content.Intent;
|
||||
import com.klinker.android.send_message.Transaction;
|
||||
import com.klinker.android.send_message.Utils;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.SMSHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MmsSentReceiver extends com.klinker.android.send_message.MmsSentReceiver {
|
||||
|
||||
@Override
|
||||
@ -41,5 +45,23 @@ public class MmsSentReceiver extends com.klinker.android.send_message.MmsSentRec
|
||||
|
||||
@Override
|
||||
public void onMessageStatusUpdated(Context context, Intent intent, int resultCode) {
|
||||
SMSHelper.Message message = SMSHelper.getNewestMessage(context);
|
||||
|
||||
ArrayList<String> addressList = new ArrayList<>();
|
||||
for (SMSHelper.Address address : message.addresses) {
|
||||
addressList.add(address.toString());
|
||||
}
|
||||
|
||||
Intent repliedNotification = new Intent(context, NotificationReplyReceiver.class);
|
||||
repliedNotification.setAction(NotificationReplyReceiver.SMS_MMS_REPLY_ACTION);
|
||||
repliedNotification.putExtra(NotificationReplyReceiver.TEXT_BODY, message.body);
|
||||
repliedNotification.putExtra(NotificationReplyReceiver.NOTIFICATION_ID, Integer.parseInt(message.threadID.toString()));
|
||||
repliedNotification.putExtra(NotificationReplyReceiver.ADDRESS_LIST, addressList);
|
||||
|
||||
// SEND_ACTION value is required to differentiate between the intents sent from reply action or
|
||||
// SentReceivers inorder to avoid posting duplicate notifications
|
||||
repliedNotification.putExtra(NotificationReplyReceiver.SEND_ACTION, false);
|
||||
|
||||
context.sendBroadcast(repliedNotification);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright 2020 Aniket Kumar <anikketkumar786@gmail.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 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.SMSPlugin;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.Person;
|
||||
import androidx.core.app.RemoteInput;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
|
||||
import com.klinker.android.send_message.Utils;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.ContactsHelper;
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect.Helpers.SMSHelper;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
public class NotificationReplyReceiver extends BroadcastReceiver {
|
||||
public static final String SMS_MMS_REPLY_ACTION = "org.kde.kdeconnect.Plugins.SMSPlugin.sms_mms_reply_action";
|
||||
public static final String SMS_MMS_MARK_ACTION = "org.kde.kdeconnect.Plugins.SMSPlugin.sms_mms_mark_action";
|
||||
public static final String ADDRESS_LIST = "address_list";
|
||||
public static final String CHANNEL_ID = NotificationHelper.Channels.SMS_MMS;
|
||||
public static final String KEY_TEXT_REPLY = "key_text_reply";
|
||||
public static final String NOTIFICATION_ID = "notification_id";
|
||||
public static final String SEND_ACTION = "send_action";
|
||||
public static final String TEXT_BODY = "text_body";
|
||||
public static final String SMS_NOTIFICATION_GROUP_KEY = "Plugins.SMSPlugin.sms_notification_group_key";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!Utils.isDefaultSmsApp(context) || intent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||
|
||||
final int notificationId = intent.getIntExtra(NotificationReplyReceiver.NOTIFICATION_ID, 0);
|
||||
final ArrayList<String> addressList = intent.getStringArrayListExtra(ADDRESS_LIST);
|
||||
final boolean sentUsingReplyButton = intent.getBooleanExtra(SEND_ACTION, false);
|
||||
|
||||
if (intent.getAction().equals(SMS_MMS_REPLY_ACTION)) {
|
||||
String inputString = null;
|
||||
|
||||
if (sentUsingReplyButton) {
|
||||
inputString = remoteInput.getCharSequence(NotificationReplyReceiver.KEY_TEXT_REPLY).toString();
|
||||
|
||||
ArrayList<SMSHelper.Address> addresses = new ArrayList<>();
|
||||
for (String address : addressList) {
|
||||
addresses.add(new SMSHelper.Address(address));
|
||||
}
|
||||
SmsMmsUtils.sendMessage(context, inputString, addresses, -1);
|
||||
} else {
|
||||
inputString = intent.getStringExtra(TEXT_BODY);
|
||||
repliedMessageNotification(context, notificationId, inputString, addressList);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the conversation as read
|
||||
if (intent.getAction().equals(SMS_MMS_MARK_ACTION)) {
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(notificationId);
|
||||
SmsMmsUtils.markConversationRead(context, new HashSet<>(addressList));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the active notification with the newly replied message
|
||||
*/
|
||||
private void repliedMessageNotification(Context context, int notificationId, String inputString, ArrayList<String> addressList) {
|
||||
Person sender = new Person.Builder()
|
||||
.setName(context.getString(R.string.user_display_name))
|
||||
.build();
|
||||
|
||||
// Create pending intent for reply action through notification
|
||||
PendingIntent replyPendingIntent = createReplyPendingIntent(context, addressList, notificationId, true);
|
||||
|
||||
RemoteInput remoteReplyInput = new RemoteInput.Builder(NotificationReplyReceiver.KEY_TEXT_REPLY)
|
||||
.setLabel(context.getString(R.string.message_reply_label))
|
||||
.build();
|
||||
|
||||
NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(0, context.getString(R.string.message_reply_label), replyPendingIntent)
|
||||
.addRemoteInput(remoteReplyInput)
|
||||
.setAllowGeneratedReplies(true)
|
||||
.build();
|
||||
|
||||
// Create pending intent for marking the message as read in database through mark as read action
|
||||
PendingIntent markAsReadPendingIntent = createMarkAsReadPendingIntent(context, addressList, notificationId);
|
||||
|
||||
NotificationCompat.Action markAsReadAction = new NotificationCompat.Action.Builder(0, context.getString(R.string.mark_as_read_label), markAsReadPendingIntent)
|
||||
.build();
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationCompat.MessagingStyle.Message message = new NotificationCompat.MessagingStyle.Message(
|
||||
inputString,
|
||||
System.currentTimeMillis(),
|
||||
sender
|
||||
);
|
||||
|
||||
NotificationCompat.MessagingStyle messagingStyle = restoreActiveMessagingStyle(notificationId, notificationManager);
|
||||
|
||||
if (messagingStyle == null) {
|
||||
// Return when there is no active notification in the statusBar with the above notificationId
|
||||
return;
|
||||
}
|
||||
|
||||
messagingStyle.addMessage(message);
|
||||
|
||||
NotificationCompat.Builder repliedNotification = new NotificationCompat.Builder(context, NotificationReplyReceiver.CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_baseline_sms_24)
|
||||
.setColor(ContextCompat.getColor(context, R.color.primary))
|
||||
.setOnlyAlertOnce(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setStyle(messagingStyle)
|
||||
.setAutoCancel(true)
|
||||
.addAction(replyAction)
|
||||
.addAction(markAsReadAction)
|
||||
.setGroup(NotificationReplyReceiver.SMS_NOTIFICATION_GROUP_KEY);
|
||||
|
||||
NotificationHelper.notifyCompat(notificationManager, notificationId, repliedNotification.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a new messaging style for newer conversations and if there is already an active notification
|
||||
* of the same id, it just adds to the previous and returns the modified messagingStyle object.
|
||||
*/
|
||||
public static NotificationCompat.MessagingStyle createMessagingStyle(
|
||||
Context context,
|
||||
int notificationId,
|
||||
String textMessage,
|
||||
String phoneNumbers,
|
||||
long date,
|
||||
Person sender,
|
||||
NotificationManager notificationManager
|
||||
) {
|
||||
NotificationCompat.MessagingStyle messageStyle = NotificationReplyReceiver.restoreActiveMessagingStyle(
|
||||
notificationId,
|
||||
notificationManager
|
||||
);
|
||||
|
||||
NotificationCompat.MessagingStyle.Message message = new NotificationCompat.MessagingStyle.Message(
|
||||
textMessage,
|
||||
date,
|
||||
sender
|
||||
);
|
||||
|
||||
if (messageStyle == null) {
|
||||
// When no active notification is found for matching conversation create a new one
|
||||
String senderName = phoneNumbers;
|
||||
Map<String, String> contactInfo = ContactsHelper.phoneNumberLookup(context, phoneNumbers);
|
||||
|
||||
if (contactInfo.containsKey("name")) {
|
||||
senderName = contactInfo.get("name");
|
||||
}
|
||||
|
||||
messageStyle = new NotificationCompat.MessagingStyle(sender)
|
||||
.setConversationTitle(senderName);
|
||||
}
|
||||
messageStyle.addMessage(message);
|
||||
|
||||
return messageStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible for searching the notification for same conversation ID and if there is an active notification
|
||||
* of save ID found in the status menu it extracts and returns the messagingStyle object of that notification
|
||||
*/
|
||||
public static NotificationCompat.MessagingStyle restoreActiveMessagingStyle(
|
||||
int notificationId,
|
||||
NotificationManager notificationManager
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
StatusBarNotification notifications[] = notificationManager.getActiveNotifications();
|
||||
for (StatusBarNotification notification : notifications) {
|
||||
if (notification.getId() == notificationId) {
|
||||
return NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(notification.getNotification());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the sender of the message as a Person object
|
||||
*/
|
||||
public static Person getMessageSender(Context context, String address) {
|
||||
Map<String, String> contactInfo = ContactsHelper.phoneNumberLookup(context, address);
|
||||
String senderName = address;
|
||||
|
||||
if (contactInfo.containsKey("name")) {
|
||||
senderName = contactInfo.get("name");
|
||||
}
|
||||
|
||||
Bitmap contactPhoto = null;
|
||||
if (contactInfo.containsKey("photoID")) {
|
||||
String photoUri = contactInfo.get("photoID");
|
||||
if (photoUri != null) {
|
||||
try {
|
||||
String base64photo = ContactsHelper.photoId64Encoded(context, photoUri);
|
||||
if (!TextUtils.isEmpty(base64photo)) {
|
||||
byte[] decodedString = Base64.decode(base64photo, Base64.DEFAULT);
|
||||
contactPhoto = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("SMS Notification", "Failed to get contact photo");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Person.Builder personBuilder = new Person.Builder()
|
||||
.setName(senderName);
|
||||
|
||||
if (contactPhoto != null) {
|
||||
personBuilder.setIcon(IconCompat.createWithBitmap(contactPhoto));
|
||||
}
|
||||
|
||||
return personBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create pending intent for reply action through notification
|
||||
*/
|
||||
public static PendingIntent createReplyPendingIntent(
|
||||
Context context,
|
||||
ArrayList<String> addressList,
|
||||
int notificationId,
|
||||
boolean isFromSendAction
|
||||
) {
|
||||
Intent replyIntent = new Intent(context, NotificationReplyReceiver.class);
|
||||
replyIntent.setAction(NotificationReplyReceiver.SMS_MMS_REPLY_ACTION);
|
||||
replyIntent.putExtra(NotificationReplyReceiver.NOTIFICATION_ID, notificationId);
|
||||
replyIntent.putExtra(NotificationReplyReceiver.ADDRESS_LIST, addressList);
|
||||
replyIntent.putExtra(NotificationReplyReceiver.SEND_ACTION, isFromSendAction);
|
||||
|
||||
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
notificationId,
|
||||
replyIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
|
||||
return replyPendingIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create pending intent for marking the message as read in database through mark as read action
|
||||
*/
|
||||
public static PendingIntent createMarkAsReadPendingIntent(
|
||||
Context context,
|
||||
ArrayList<String> addressList,
|
||||
int notificationId
|
||||
) {
|
||||
Intent markAsReadIntent = new Intent(context, NotificationReplyReceiver.class);
|
||||
markAsReadIntent.setAction(NotificationReplyReceiver.SMS_MMS_MARK_ACTION);
|
||||
markAsReadIntent.putExtra(NotificationReplyReceiver.NOTIFICATION_ID, notificationId);
|
||||
markAsReadIntent.putExtra(NotificationReplyReceiver.ADDRESS_LIST, addressList);
|
||||
|
||||
PendingIntent markAsReadPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
notificationId,
|
||||
markAsReadIntent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT
|
||||
);
|
||||
|
||||
return markAsReadPendingIntent;
|
||||
}
|
||||
}
|
@ -32,12 +32,21 @@ import com.klinker.android.send_message.Settings;
|
||||
import com.klinker.android.send_message.Transaction;
|
||||
import com.klinker.android.send_message.Utils;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.provider.Telephony;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.kde.kdeconnect.Helpers.SMSHelper;
|
||||
import org.kde.kdeconnect.Helpers.TelephonyHelper;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
public class SmsMmsUtils {
|
||||
@ -119,4 +128,38 @@ public class SmsMmsUtils {
|
||||
com.klinker.android.logger.Log.e(SENDING_MESSAGE, "Exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a conversation as read in the database.
|
||||
*
|
||||
* @param context the context to get the content provider with.
|
||||
* @param recipients the phone numbers to find the conversation with.
|
||||
*/
|
||||
public static void markConversationRead(Context context, HashSet<String> recipients) {
|
||||
new Thread() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long threadId = Utils.getOrCreateThreadId(context, recipients);
|
||||
markAsRead(context, ContentUris.withAppendedId(Telephony.Threads.CONTENT_URI, threadId), threadId);
|
||||
} catch (Exception e) {
|
||||
// the conversation doesn't exist
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private static void markAsRead(Context context, Uri uri, long threadId) {
|
||||
Log.v("SMSPlugin", "marking thread with threadId " + threadId + " as read at Uri" + uri);
|
||||
|
||||
if (uri != null && context != null) {
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put("read", 1);
|
||||
values.put("seen", 1);
|
||||
|
||||
context.getContentResolver().update(uri, values, "(read=0 OR seen=0)", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@
|
||||
|
||||
package org.kde.kdeconnect.Plugins.SMSPlugin;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -29,10 +31,22 @@ import android.telephony.SmsMessage;
|
||||
import android.provider.Telephony.Sms;
|
||||
import android.net.Uri;
|
||||
import android.content.ContentValues;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.Person;
|
||||
import androidx.core.app.RemoteInput;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.klinker.android.send_message.Transaction;
|
||||
import com.klinker.android.send_message.Utils;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SmsReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
|
||||
@ -75,8 +89,77 @@ public class SmsReceiver extends BroadcastReceiver {
|
||||
Intent refreshIntent = new Intent(Transaction.REFRESH);
|
||||
context.sendBroadcast(refreshIntent);
|
||||
}
|
||||
|
||||
String body = message[i].getMessageBody();
|
||||
String phoneNo = message[i].getOriginatingAddress();
|
||||
long date = message[i].getTimestampMillis();
|
||||
|
||||
createSmsNotification(context, body, phoneNo, date);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createSmsNotification(Context context, String body, String phoneNo, long date) {
|
||||
int notificationId;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
notificationId = (int) Utils.getOrCreateThreadId(context, phoneNo);
|
||||
} else {
|
||||
notificationId = (int) System.currentTimeMillis();
|
||||
}
|
||||
|
||||
Person sender = NotificationReplyReceiver.getMessageSender(context, phoneNo);
|
||||
|
||||
ArrayList<String> addressList = new ArrayList<>(Arrays.asList(phoneNo));
|
||||
|
||||
// Create pending intent for reply action through notification
|
||||
PendingIntent replyPendingIntent = NotificationReplyReceiver.createReplyPendingIntent(
|
||||
context,
|
||||
addressList,
|
||||
notificationId,
|
||||
true
|
||||
);
|
||||
|
||||
RemoteInput remoteReplyInput = new RemoteInput.Builder(NotificationReplyReceiver.KEY_TEXT_REPLY)
|
||||
.setLabel(context.getString(R.string.message_reply_label))
|
||||
.build();
|
||||
|
||||
NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(0, context.getString(R.string.message_reply_label), replyPendingIntent)
|
||||
.addRemoteInput(remoteReplyInput)
|
||||
.setAllowGeneratedReplies(true)
|
||||
.build();
|
||||
|
||||
// Create pending intent for marking the message as read in database through mark as read action
|
||||
PendingIntent markAsReadPendingIntent = NotificationReplyReceiver.createMarkAsReadPendingIntent(
|
||||
context,
|
||||
addressList,
|
||||
notificationId
|
||||
);
|
||||
|
||||
NotificationCompat.Action markAsReadAction = new NotificationCompat.Action.Builder(0, context.getString(R.string.mark_as_read_label), markAsReadPendingIntent)
|
||||
.build();
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationCompat.MessagingStyle messagingStyle = NotificationReplyReceiver.createMessagingStyle(
|
||||
context,
|
||||
notificationId,
|
||||
body,
|
||||
phoneNo,
|
||||
date,
|
||||
sender,
|
||||
notificationManager
|
||||
);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationReplyReceiver.CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_baseline_sms_24)
|
||||
.setColor(ContextCompat.getColor(context, R.color.primary))
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setStyle(messagingStyle)
|
||||
.setAutoCancel(true)
|
||||
.addAction(replyAction)
|
||||
.addAction(markAsReadAction)
|
||||
.setGroup(NotificationReplyReceiver.SMS_NOTIFICATION_GROUP_KEY);
|
||||
|
||||
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,10 @@ import com.klinker.android.send_message.SentReceiver;
|
||||
import com.klinker.android.send_message.Transaction;
|
||||
import com.klinker.android.send_message.Utils;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.SMSHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SmsSentReceiver extends SentReceiver {
|
||||
|
||||
@Override
|
||||
@ -42,5 +46,23 @@ public class SmsSentReceiver extends SentReceiver {
|
||||
|
||||
@Override
|
||||
public void onMessageStatusUpdated(Context context, Intent intent, int receiverResultCode) {
|
||||
SMSHelper.Message message = SMSHelper.getNewestMessage(context);
|
||||
|
||||
ArrayList<String> addressList = new ArrayList<>();
|
||||
for (SMSHelper.Address address : message.addresses) {
|
||||
addressList.add(address.toString());
|
||||
}
|
||||
|
||||
Intent repliedNotification = new Intent(context, NotificationReplyReceiver.class);
|
||||
repliedNotification.setAction(NotificationReplyReceiver.SMS_MMS_REPLY_ACTION);
|
||||
repliedNotification.putExtra(NotificationReplyReceiver.TEXT_BODY, message.body);
|
||||
repliedNotification.putExtra(NotificationReplyReceiver.NOTIFICATION_ID, Integer.parseInt(message.threadID.toString()));
|
||||
repliedNotification.putExtra(NotificationReplyReceiver.ADDRESS_LIST, addressList);
|
||||
|
||||
// SEND_ACTION value is required to differentiate between the intents sent from reply action or
|
||||
// SentReceivers inorder to avoid posting duplicate notifications
|
||||
repliedNotification.putExtra(NotificationReplyReceiver.SEND_ACTION, false);
|
||||
|
||||
context.sendBroadcast(repliedNotification);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user