mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 18:07:55 +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:enabled="true"
|
||||||
android:taskAffinity="com.klinker.android.messaging.MMS_SENT" />
|
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
|
<service
|
||||||
android:name="org.kde.kdeconnect.Plugins.SMSPlugin.HeadlessSmsSendService"
|
android:name="org.kde.kdeconnect.Plugins.SMSPlugin.HeadlessSmsSendService"
|
||||||
android:exported="true"
|
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_media_control">Media control</string>
|
||||||
<string name="notification_channel_filetransfer">File transfer</string>
|
<string name="notification_channel_filetransfer">File transfer</string>
|
||||||
<string name="notification_channel_high_priority">High priority</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="mpris_stop">Stop the current player</string>
|
||||||
<string name="copy_url_to_clipboard">Copy URL to clipboard</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_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="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_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_title">Send group MMS</string>
|
||||||
<string name="set_group_message_as_mms" translatable="false">set_group_message_as_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 DEFAULT = "default";
|
||||||
public final static String MEDIA_CONTROL = "media_control";
|
public final static String MEDIA_CONTROL = "media_control";
|
||||||
public final static String FILETRANSFER = "filetransfer";
|
public final static String FILETRANSFER = "filetransfer";
|
||||||
|
public final static String SMS_MMS = "sms_mms";
|
||||||
public final static String RECEIVENOTIFICATION = "receive";
|
public final static String RECEIVENOTIFICATION = "receive";
|
||||||
public final static String HIGHPRIORITY = "highpriority";
|
public final static String HIGHPRIORITY = "highpriority";
|
||||||
}
|
}
|
||||||
@ -83,6 +84,12 @@ public class NotificationHelper {
|
|||||||
NotificationManager.IMPORTANCE_DEFAULT)
|
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);
|
NotificationChannel highPriority = new NotificationChannel(Channels.HIGHPRIORITY, context.getString(R.string.notification_channel_high_priority), NotificationManager.IMPORTANCE_HIGH);
|
||||||
manager.createNotificationChannel(highPriority);
|
manager.createNotificationChannel(highPriority);
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,7 @@ import org.kde.kdeconnect.Helpers.AppsHelper;
|
|||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
import org.kde.kdeconnect.Plugins.Plugin;
|
import org.kde.kdeconnect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||||
|
import org.kde.kdeconnect.Plugins.SMSPlugin.NotificationReplyReceiver;
|
||||||
import org.kde.kdeconnect.UserInterface.MainActivity;
|
import org.kde.kdeconnect.UserInterface.MainActivity;
|
||||||
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
||||||
import org.kde.kdeconnect.UserInterface.StartActivityAlertDialogFragment;
|
import org.kde.kdeconnect.UserInterface.StartActivityAlertDialogFragment;
|
||||||
@ -203,7 +204,6 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
|||||||
String packageName = statusBarNotification.getPackageName();
|
String packageName = statusBarNotification.getPackageName();
|
||||||
String appName = AppsHelper.appNameLookup(context, packageName);
|
String appName = AppsHelper.appNameLookup(context, packageName);
|
||||||
|
|
||||||
|
|
||||||
if ("com.facebook.orca".equals(packageName) &&
|
if ("com.facebook.orca".equals(packageName) &&
|
||||||
(statusBarNotification.getId() == 10012) &&
|
(statusBarNotification.getId() == 10012) &&
|
||||||
"Messenger".equals(appName) &&
|
"Messenger".equals(appName) &&
|
||||||
@ -219,8 +219,18 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("org.kde.kdeconnect_tp".equals(packageName)) {
|
if ("org.kde.kdeconnect_tp".equals(packageName)) {
|
||||||
// Don't send our own notifications
|
// Don't send our own notifications except notifications posted by SMSPlugin
|
||||||
return;
|
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);
|
NetworkPacket np = new NetworkPacket(PACKET_TYPE_NOTIFICATION);
|
||||||
|
@ -24,17 +24,31 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
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.Transaction;
|
||||||
import com.klinker.android.send_message.Utils;
|
import com.klinker.android.send_message.Utils;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Helpers.TelephonyHelper;
|
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
|
* 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
|
* persist the message to the internal database and it will also show a notification in the status bar.
|
||||||
* implemented later.
|
|
||||||
*/
|
*/
|
||||||
public class MmsReceivedReceiver extends com.klinker.android.send_message.MmsReceivedReceiver {
|
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
|
// Notify messageUpdateReceiver about the arrival of the new MMS message
|
||||||
Intent refreshIntent = new Intent(Transaction.REFRESH);
|
Intent refreshIntent = new Intent(Transaction.REFRESH);
|
||||||
context.sendBroadcast(refreshIntent);
|
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
|
@Override
|
||||||
public void onError(Context context, String error) {
|
public void onError(Context context, String error) {
|
||||||
Log.v("MmsReceived", "error: " + 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) {
|
public void getPreferredApn(Context context, Intent intent) {
|
||||||
@ -80,4 +106,72 @@ public class MmsReceivedReceiver extends com.klinker.android.send_message.MmsRec
|
|||||||
}
|
}
|
||||||
return null;
|
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.Transaction;
|
||||||
import com.klinker.android.send_message.Utils;
|
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 {
|
public class MmsSentReceiver extends com.klinker.android.send_message.MmsSentReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -41,5 +45,23 @@ public class MmsSentReceiver extends com.klinker.android.send_message.MmsSentRec
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessageStatusUpdated(Context context, Intent intent, int resultCode) {
|
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.Transaction;
|
||||||
import com.klinker.android.send_message.Utils;
|
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.apache.commons.lang3.ArrayUtils;
|
||||||
import org.kde.kdeconnect.Helpers.SMSHelper;
|
import org.kde.kdeconnect.Helpers.SMSHelper;
|
||||||
import org.kde.kdeconnect.Helpers.TelephonyHelper;
|
import org.kde.kdeconnect.Helpers.TelephonyHelper;
|
||||||
import org.kde.kdeconnect_tp.R;
|
import org.kde.kdeconnect_tp.R;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SmsMmsUtils {
|
public class SmsMmsUtils {
|
||||||
@ -119,4 +128,38 @@ public class SmsMmsUtils {
|
|||||||
com.klinker.android.logger.Log.e(SENDING_MESSAGE, "Exception", e);
|
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;
|
package org.kde.kdeconnect.Plugins.SMSPlugin;
|
||||||
|
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -29,10 +31,22 @@ import android.telephony.SmsMessage;
|
|||||||
import android.provider.Telephony.Sms;
|
import android.provider.Telephony.Sms;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.content.ContentValues;
|
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.Transaction;
|
||||||
import com.klinker.android.send_message.Utils;
|
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 {
|
public class SmsReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
|
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);
|
Intent refreshIntent = new Intent(Transaction.REFRESH);
|
||||||
context.sendBroadcast(refreshIntent);
|
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.Transaction;
|
||||||
import com.klinker.android.send_message.Utils;
|
import com.klinker.android.send_message.Utils;
|
||||||
|
|
||||||
|
import org.kde.kdeconnect.Helpers.SMSHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class SmsSentReceiver extends SentReceiver {
|
public class SmsSentReceiver extends SentReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -42,5 +46,23 @@ public class SmsSentReceiver extends SentReceiver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessageStatusUpdated(Context context, Intent intent, int receiverResultCode) {
|
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