2018-08-15 17:12:36 +02:00
/ *
2020-08-17 16:17:20 +02:00
* SPDX - FileCopyrightText : 2014 Albert Vaca Cintora < albertvaka @gmail.com >
2021-03-09 09:00:24 -08:00
* SPDX - FileCopyrightText : 2021 Simon Redman < simon @ergotech.com >
* SPDX - FileCopyrightText : 2020 Aniket Kumar < anikketkumar786 @gmail.com >
2018-08-15 17:12:36 +02:00
*
2020-08-17 16:17:20 +02:00
* SPDX - License - Identifier : GPL - 2 . 0 - only OR GPL - 3 . 0 - only OR LicenseRef - KDE - Accepted - GPL
2018-08-15 17:12:36 +02:00
* /
package org.kde.kdeconnect.Plugins.SMSPlugin ;
import android.Manifest ;
2019-06-05 22:13:59 -06:00
import android.annotation.SuppressLint ;
2018-08-15 17:12:36 +02:00
import android.content.BroadcastReceiver ;
import android.content.Context ;
import android.content.Intent ;
import android.content.IntentFilter ;
import android.content.SharedPreferences ;
import android.content.pm.PackageManager ;
2018-09-17 08:52:54 -06:00
import android.database.ContentObserver ;
2019-04-19 23:55:46 +02:00
import android.os.Build ;
2018-08-15 17:12:36 +02:00
import android.os.Bundle ;
2018-09-17 08:52:54 -06:00
import android.os.Handler ;
import android.os.Looper ;
2018-08-15 17:12:36 +02:00
import android.preference.PreferenceManager ;
2019-03-20 13:36:52 -06:00
import android.provider.Telephony ;
2018-08-15 17:12:36 +02:00
import android.telephony.PhoneNumberUtils ;
import android.telephony.SmsManager ;
import android.telephony.SmsMessage ;
import org.json.JSONArray ;
import org.json.JSONException ;
import org.json.JSONObject ;
import org.kde.kdeconnect.Helpers.ContactsHelper ;
import org.kde.kdeconnect.Helpers.SMSHelper ;
import org.kde.kdeconnect.NetworkPacket ;
import org.kde.kdeconnect.Plugins.Plugin ;
2019-03-20 23:11:58 +01:00
import org.kde.kdeconnect.Plugins.PluginFactory ;
2018-08-15 17:12:36 +02:00
import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin ;
import org.kde.kdeconnect_tp.BuildConfig ;
import org.kde.kdeconnect_tp.R ;
import java.util.ArrayList ;
2018-09-17 08:52:54 -06:00
import java.util.Collection ;
2018-08-15 17:12:36 +02:00
import java.util.List ;
import java.util.Map ;
2018-09-17 08:52:54 -06:00
import java.util.concurrent.locks.Lock ;
import java.util.concurrent.locks.ReentrantLock ;
2018-08-15 17:12:36 +02:00
2018-12-27 15:40:57 +01:00
import androidx.core.content.ContextCompat ;
2020-07-05 13:32:44 +05:30
import com.klinker.android.send_message.ApnUtils ;
import com.klinker.android.send_message.Transaction ;
import com.klinker.android.send_message.Utils ;
import com.klinker.android.logger.Log ;
2018-08-15 17:12:36 +02:00
import static org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin.PACKET_TYPE_TELEPHONY ;
2019-03-20 23:11:58 +01:00
@PluginFactory.LoadablePlugin
2019-06-05 22:13:59 -06:00
@SuppressLint ( " InlinedApi " )
2018-08-15 17:12:36 +02:00
public class SMSPlugin extends Plugin {
/ * *
* Packet used to indicate a batch of messages has been pushed from the remote device
2018-09-29 19:21:41 +02:00
* < p >
2018-08-15 17:12:36 +02:00
* The body should contain the key " messages " mapping to an array of messages
2018-09-29 19:21:41 +02:00
* < p >
2018-08-15 17:12:36 +02:00
* For example :
2019-07-19 17:46:54 +00:00
* {
* " version " : 2 // This is the second version of this packet type and
* // version 1 packets (which did not carry this flag)
* // are incompatible with the new format
* " messages " : [
2018-11-15 16:56:14 -07:00
* { " event " : 1 , // 32-bit field containing a bitwise-or of event flags
* // See constants declared in SMSHelper.Message for defined
* // values and explanations
* " body " : " Hello " , // Text message body
2019-07-19 17:46:54 +00:00
* " addresses " : < List < Address > > // List of Address objects, one for each participant of the conversation
* // The user's Address is excluded so:
* // If this is a single-target messsage, there will only be one
* // Address (the other party)
* // If this is an incoming multi-target message, the first Address is the
* // sender and all other addresses are other parties to the conversation
* // If this is an outgoing multi-target message, the sender is implicit
* // (the user's phone number) and all Addresses are recipients
2018-11-15 16:56:14 -07:00
* " date " : " 1518846484880 " , // Timestamp of the message
* " type " : " 2 " , // Compare with Android's
* // Telephony.TextBasedSmsColumns.MESSAGE_TYPE_*
2019-07-19 17:46:54 +00:00
* " thread_id " : 132 // Thread to which the message belongs
2018-11-15 16:56:14 -07:00
* " read " : true // Boolean representing whether a message is read or unread
2018-11-01 12:51:57 -06:00
* } ,
* { . . . } ,
* . . .
2018-09-29 19:21:41 +02:00
* ]
2019-07-19 17:46:54 +00:00
*
* The following optional fields of a message object may be defined
* " sub_id " : < int > // Android's subscriber ID, which is basically used to determine which SIM card the message
* // belongs to. This is mostly useful when attempting to reply to an SMS with the correct
* // SIM card using PACKET_TYPE_SMS_REQUEST.
* // If this value is not defined or if it does not match a valid subscriber_id known by
* // Android, we will use whatever subscriber ID Android gives us as the default
*
2020-08-12 17:08:02 +05:30
* " attachments " : < List < Attachment > > // List of Attachment objects, one for each attached file in the message.
*
* An Attachment object looks like :
* {
* " part_id " : < long > // part_id of the attachment used to read the file from MMS database
2021-07-24 19:16:12 -07:00
* " mime_type " : < String > // contains the mime type of the file (eg: image/jpg, video/mp4 etc.)
2020-08-12 17:08:02 +05:30
* " encoded_thumbnail " : < String > // Optional base64-encoded thumbnail preview of the content for types which support it
* " unique_identifier " : < String > // Unique name of te file
* }
*
2019-07-19 17:46:54 +00:00
* An Address object looks like :
* {
* " address " : < String > // Address (phone number, email address, etc.) of this object
* }
2018-08-15 17:12:36 +02:00
* /
2018-09-16 16:05:01 -06:00
private final static String PACKET_TYPE_SMS_MESSAGE = " kdeconnect.sms.messages " ;
2019-07-19 17:46:54 +00:00
private final static int SMS_MESSAGE_PACKET_VERSION = 2 ; // We *send* packets of this version
2018-09-16 16:05:01 -06:00
/ * *
* Packet sent to request a message be sent
2021-03-09 09:00:24 -08:00
*
2018-09-16 16:05:01 -06:00
* The body should look like so :
2020-10-09 00:36:03 +00:00
* {
* " version " : 2 , // The version of the packet being sent. Compare to SMS_REQUEST_PACKET_VERSION before attempting to handle.
* " sendSms " : true , // (Depreciated, ignored) Old versions of the desktop app used to mix phone calls, SMS, etc. in the same packet type and used this field to differentiate.
* " phoneNumber " : " 542904563213 " , // (Depreciated) Retained for backwards-compatibility. Old versions of the desktop app send a single phoneNumber. Use the Addresses field instead.
* " addresses " : < List of Addresses > , // The one or many targets of this message
* " messageBody " : " Hi mom! " , // Plain-text string to be sent as the body of the message (Optional if sending an attachment)
* " attachments " : < List of Attached files > ,
* " sub_id " : 3859358340534 // Some magic number which tells Android which SIM card to use (Optional, if omitted, sends with the default SIM card)
* }
*
* An AttachmentContainer object looks like :
* {
* " fileName " : < String > // Name of the file
* " base64EncodedFile " : < String > // Base64 encoded file
* " mimeType " : < String > // File type (eg: image/jpg, video/mp4 etc.)
2018-09-16 16:05:01 -06:00
* }
* /
private final static String PACKET_TYPE_SMS_REQUEST = " kdeconnect.sms.request " ;
2020-10-09 00:36:03 +00:00
private final static int SMS_REQUEST_PACKET_VERSION = 2 ; // We *handle* packets of this version or lower. Update this number only if future packets break backwards-compatibility.
2018-08-15 17:12:36 +02:00
/ * *
2018-09-16 16:05:01 -06:00
* Packet sent to request the most - recent message in each conversations on the device
2018-09-29 19:21:41 +02:00
* < p >
2018-08-15 17:12:36 +02:00
* The request packet shall contain no body
* /
2018-09-16 16:05:01 -06:00
private final static String PACKET_TYPE_SMS_REQUEST_CONVERSATIONS = " kdeconnect.sms.request_conversations " ;
2018-08-15 17:12:36 +02:00
/ * *
* Packet sent to request all the messages in a particular conversation
2018-09-29 19:21:41 +02:00
* < p >
2020-03-11 00:34:31 +00:00
* The following fields are available :
* " threadID " : < long > // (Required) ThreadID to request
* " rangeStartTimestamp " : < long > // (Optional) Millisecond epoch timestamp indicating the start of the range from which to return messages
* " numberToRequest " : < long > // (Optional) Number of messages to return, starting from rangeStartTimestamp.
* // May return fewer than expected if there are not enough or more than expected if many
* // messages have the same timestamp.
2018-08-15 17:12:36 +02:00
* /
2018-09-16 16:05:01 -06:00
private final static String PACKET_TYPE_SMS_REQUEST_CONVERSATION = " kdeconnect.sms.request_conversation " ;
2018-08-15 17:12:36 +02:00
2020-08-31 14:39:57 +05:30
/ * *
* Packet sent to request an attachment file in a particular message of a conversation
* < p >
* The body should look like so :
* " part_id " : < long > // Part id of the attachment
* " unique_identifier " : < String > // This unique_identifier should come from a previous message packet's attachment field
* /
private final static String PACKET_TYPE_SMS_REQUEST_ATTACHMENT = " kdeconnect.sms.request_attachment " ;
/ * *
* Packet used to send original attachment file from mms database to desktop
* < p >
* The following fields are available :
* " filename " : < String > // Name of the attachment file in the database
* " payload " : // Actual attachment file to be transferred
* /
private final static String PACKET_TYPE_SMS_ATTACHMENT_FILE = " kdeconnect.sms.attachment_file " ;
2018-08-15 17:12:36 +02:00
private static final String KEY_PREF_BLOCKED_NUMBERS = " telephony_blocked_numbers " ;
private final BroadcastReceiver receiver = new BroadcastReceiver ( ) {
@Override
public void onReceive ( Context context , Intent intent ) {
String action = intent . getAction ( ) ;
//Log.e("TelephonyPlugin","Telephony event: " + action);
2019-03-20 13:36:52 -06:00
if ( Telephony . Sms . Intents . SMS_RECEIVED_ACTION . equals ( action ) ) {
2018-08-15 17:12:36 +02:00
final Bundle bundle = intent . getExtras ( ) ;
if ( bundle = = null ) return ;
final Object [ ] pdus = ( Object [ ] ) bundle . get ( " pdus " ) ;
ArrayList < SmsMessage > messages = new ArrayList < > ( ) ;
for ( Object pdu : pdus ) {
// I hope, but am not sure, that the pdus array is in the order that the parts
// of the SMS message should be
// If it is not, I believe the pdu contains the information necessary to put it
// in order, but in my testing the order seems to be correct, so I won't worry
// about it now.
messages . add ( SmsMessage . createFromPdu ( ( byte [ ] ) pdu ) ) ;
}
2018-09-17 08:52:54 -06:00
smsBroadcastReceivedDeprecated ( messages ) ;
2018-08-15 17:12:36 +02:00
}
}
} ;
2018-09-17 08:52:54 -06:00
/ * *
* Keep track of the most - recently - seen message so that we can query for later ones as they arrive
* /
private long mostRecentTimestamp = 0 ;
// Since the mostRecentTimestamp is accessed both from the plugin's thread and the ContentObserver
// thread, make sure that access is coherent
2018-10-27 00:01:30 +02:00
private final Lock mostRecentTimestampLock = new ReentrantLock ( ) ;
2018-09-17 08:52:54 -06:00
private class MessageContentObserver extends ContentObserver {
/ * *
* Create a ContentObserver to watch the Messages database . onChange is called for
* every subscribed change
*
* @param handler Handler object used to make the callback
* /
2019-07-04 21:01:12 +02:00
MessageContentObserver ( Handler handler ) {
2018-09-17 08:52:54 -06:00
super ( handler ) ;
}
/ * *
* The onChange method is called whenever the subscribed - to database changes
*
* In this case , this onChange expects to be called whenever * anything * in the Messages
* database changes and simply reports those updated messages to anyone who might be listening
* /
2018-10-26 23:08:32 +02:00
@Override
2018-09-17 08:52:54 -06:00
public void onChange ( boolean selfChange ) {
2020-07-05 13:32:44 +05:30
sendLatestMessage ( ) ;
}
2018-09-17 08:52:54 -06:00
2020-07-05 13:32:44 +05:30
}
/ * *
* This receiver will be invoked only when the app will be set as the default sms app
* Whenever the app will be set as the default , the database update alert will be sent
* using messageUpdateReceiver and not the contentObserver class
* /
private final BroadcastReceiver messagesUpdateReceiver = new BroadcastReceiver ( ) {
@Override
public void onReceive ( Context context , Intent intent ) {
String action = intent . getAction ( ) ;
if ( Transaction . REFRESH . equals ( action ) ) {
sendLatestMessage ( ) ;
2018-09-17 08:52:54 -06:00
}
2020-07-05 13:32:44 +05:30
}
} ;
2018-09-17 08:52:54 -06:00
2020-07-05 13:32:44 +05:30
/ * *
* Helper method to read the latest message from the sms - mms database and sends it to the desktop
* /
private void sendLatestMessage ( ) {
// Lock so no one uses the mostRecentTimestamp between the moment we read it and the
// moment we update it. This is because reading the Messages DB can take long.
mostRecentTimestampLock . lock ( ) ;
if ( mostRecentTimestamp = = 0 ) {
// Since the timestamp has not been initialized, we know that nobody else
// has requested a message. That being the case, there is most likely
// nobody listening for message updates, so just drop them
2018-09-17 08:52:54 -06:00
mostRecentTimestampLock . unlock ( ) ;
2020-07-05 13:32:44 +05:30
return ;
}
2020-11-01 23:47:03 +00:00
List < SMSHelper . Message > messages = SMSHelper . getMessagesInRange ( context , null , mostRecentTimestamp , null , false ) ;
2018-09-17 08:52:54 -06:00
2020-11-01 23:47:03 +00:00
long newMostRecentTimestamp = mostRecentTimestamp ;
for ( SMSHelper . Message message : messages ) {
if ( message = = null | | message . date < = newMostRecentTimestamp ) {
newMostRecentTimestamp = message . date ;
}
2018-09-17 08:52:54 -06:00
}
2020-07-05 13:32:44 +05:30
// Update the most recent counter
2020-11-01 23:47:03 +00:00
mostRecentTimestamp = newMostRecentTimestamp ;
2020-07-05 13:32:44 +05:30
mostRecentTimestampLock . unlock ( ) ;
// Send the alert about the update
2020-11-01 23:47:03 +00:00
device . sendPacket ( constructBulkMessagePacket ( messages ) ) ;
2018-09-17 08:52:54 -06:00
}
/ * *
* Deliver an old - style SMS packet in response to a new message arriving
*
* For backwards - compatibility with long - lived distro packages , this method needs to exist in
* order to support older desktop apps . However , note that it should no longer be used
*
* This comment is being written 30 August 2018 . Distros will likely be running old versions for
* many years to come . . .
*
* @param messages Ordered list of parts of the message body which should be combined into a single message
* /
2018-10-26 23:08:32 +02:00
@Deprecated
2018-09-17 08:52:54 -06:00
private void smsBroadcastReceivedDeprecated ( ArrayList < SmsMessage > messages ) {
2018-08-15 17:12:36 +02:00
if ( BuildConfig . DEBUG ) {
if ( ! ( messages . size ( ) > 0 ) ) {
throw new AssertionError ( " This method requires at least one message " ) ;
}
}
NetworkPacket np = new NetworkPacket ( PACKET_TYPE_TELEPHONY ) ;
np . set ( " event " , " sms " ) ;
StringBuilder messageBody = new StringBuilder ( ) ;
for ( int index = 0 ; index < messages . size ( ) ; index + + ) {
messageBody . append ( messages . get ( index ) . getMessageBody ( ) ) ;
}
np . set ( " messageBody " , messageBody . toString ( ) ) ;
String phoneNumber = messages . get ( 0 ) . getOriginatingAddress ( ) ;
if ( isNumberBlocked ( phoneNumber ) )
return ;
int permissionCheck = ContextCompat . checkSelfPermission ( context ,
Manifest . permission . READ_CONTACTS ) ;
if ( permissionCheck = = PackageManager . PERMISSION_GRANTED ) {
Map < String , String > contactInfo = ContactsHelper . phoneNumberLookup ( context , phoneNumber ) ;
if ( contactInfo . containsKey ( " name " ) ) {
np . set ( " contactName " , contactInfo . get ( " name " ) ) ;
}
if ( contactInfo . containsKey ( " photoID " ) ) {
np . set ( " phoneThumbnail " , ContactsHelper . photoId64Encoded ( context , contactInfo . get ( " photoID " ) ) ) ;
}
}
if ( phoneNumber ! = null ) {
np . set ( " phoneNumber " , phoneNumber ) ;
}
device . sendPacket ( np ) ;
}
@Override
public boolean onCreate ( ) {
permissionExplanation = R . string . telepathy_permission_explanation ;
2019-03-20 13:36:52 -06:00
IntentFilter filter = new IntentFilter ( Telephony . Sms . Intents . SMS_RECEIVED_ACTION ) ;
2018-08-15 17:12:36 +02:00
filter . setPriority ( 500 ) ;
context . registerReceiver ( receiver , filter ) ;
2020-07-05 13:32:44 +05:30
IntentFilter refreshFilter = new IntentFilter ( Transaction . REFRESH ) ;
refreshFilter . setPriority ( 500 ) ;
context . registerReceiver ( messagesUpdateReceiver , refreshFilter ) ;
2018-09-17 08:52:54 -06:00
Looper helperLooper = SMSHelper . MessageLooper . getLooper ( ) ;
2019-07-04 21:01:12 +02:00
ContentObserver messageObserver = new MessageContentObserver ( new Handler ( helperLooper ) ) ;
2018-09-17 08:52:54 -06:00
SMSHelper . registerObserver ( messageObserver , context ) ;
2019-07-19 17:46:54 +00:00
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . KITKAT ) {
Log . w ( " SMSPlugin " , " This is a very old version of Android. The SMS Plugin might not function as intended. " ) ;
}
2020-07-05 13:32:44 +05:30
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . LOLLIPOP ) {
ApnUtils . initDefaultApns ( context , null ) ;
}
// To see debug messages for Klinker library, uncomment the below line
//Log.setDebug(true);
2018-08-15 17:12:36 +02:00
return true ;
}
@Override
public String getDisplayName ( ) {
return context . getResources ( ) . getString ( R . string . pref_plugin_telepathy ) ;
}
@Override
public String getDescription ( ) {
return context . getResources ( ) . getString ( R . string . pref_plugin_telepathy_desc ) ;
}
@Override
public boolean onPacketReceived ( NetworkPacket np ) {
2020-10-09 00:36:03 +00:00
long subID ;
2018-08-15 17:12:36 +02:00
2018-09-16 16:05:01 -06:00
switch ( np . getType ( ) ) {
case PACKET_TYPE_SMS_REQUEST_CONVERSATIONS :
2020-09-19 05:20:32 +00:00
return this . handleRequestAllConversations ( np ) ;
case PACKET_TYPE_SMS_REQUEST_CONVERSATION :
return this . handleRequestSingleConversation ( np ) ;
2018-09-16 16:05:01 -06:00
case PACKET_TYPE_SMS_REQUEST :
2020-10-09 00:36:03 +00:00
String textMessage = np . getString ( " messageBody " ) ;
subID = np . getLong ( " subID " , - 1 ) ;
List < SMSHelper . Address > addressList = SMSHelper . jsonArrayToAddressList ( np . getJSONArray ( " addresses " ) ) ;
if ( addressList = = null ) {
// If the List of Address is null, then the SMS_REQUEST packet is
// most probably from the older version of the desktop app.
addressList = new ArrayList < > ( ) ;
addressList . add ( new SMSHelper . Address ( np . getString ( " phoneNumber " ) ) ) ;
2020-07-05 13:32:44 +05:30
}
2021-03-09 09:00:24 -08:00
List < SMSHelper . Attachment > attachedFiles = SMSHelper . jsonArrayToAttachmentsList ( np . getJSONArray ( " attachments " ) ) ;
2020-10-09 00:36:03 +00:00
2021-03-09 09:00:24 -08:00
SmsMmsUtils . sendMessage ( context , textMessage , attachedFiles , addressList , ( int ) subID ) ;
2020-07-05 13:32:44 +05:30
break ;
2018-09-16 16:05:01 -06:00
case TelephonyPlugin . PACKET_TYPE_TELEPHONY_REQUEST :
if ( np . getBoolean ( " sendSms " ) ) {
String phoneNo = np . getString ( " phoneNumber " ) ;
String sms = np . getString ( " messageBody " ) ;
2020-10-09 00:36:03 +00:00
subID = np . getLong ( " subID " , - 1 ) ;
2018-09-16 16:05:01 -06:00
try {
2020-03-20 17:02:07 +02:00
SmsManager smsManager = subID = = - 1 ? SmsManager . getDefault ( ) :
SmsManager . getSmsManagerForSubscriptionId ( ( int ) subID ) ;
2018-09-16 16:05:01 -06:00
ArrayList < String > parts = smsManager . divideMessage ( sms ) ;
// If this message turns out to fit in a single SMS, sendMultipartTextMessage
// properly handles that case
smsManager . sendMultipartTextMessage ( phoneNo , null , parts , null , null ) ;
//TODO: Notify other end
} catch ( Exception e ) {
//TODO: Notify other end
2019-03-31 20:09:44 +02:00
Log . e ( " SMSPlugin " , " Exception " , e ) ;
2018-09-16 16:05:01 -06:00
}
}
break ;
2020-08-31 14:39:57 +05:30
case PACKET_TYPE_SMS_REQUEST_ATTACHMENT :
long partID = np . getLong ( " part_id " ) ;
String uniqueIdentifier = np . getString ( " unique_identifier " ) ;
NetworkPacket networkPacket = SmsMmsUtils . partIdToMessageAttachmentPacket (
context ,
partID ,
uniqueIdentifier ,
PACKET_TYPE_SMS_ATTACHMENT_FILE
) ;
if ( networkPacket ! = null ) {
device . sendPacket ( networkPacket ) ;
}
break ;
2018-08-15 17:12:36 +02:00
}
return true ;
}
/ * *
2018-09-17 08:52:54 -06:00
* Construct a proper packet of PACKET_TYPE_SMS_MESSAGE from the passed messages
*
* @param messages Messages to include in the packet
* @return NetworkPacket of type PACKET_TYPE_SMS_MESSAGE
2018-08-15 17:12:36 +02:00
* /
2018-10-26 23:53:58 +02:00
private static NetworkPacket constructBulkMessagePacket ( Collection < SMSHelper . Message > messages ) {
2018-09-16 16:05:01 -06:00
NetworkPacket reply = new NetworkPacket ( PACKET_TYPE_SMS_MESSAGE ) ;
2018-08-15 17:12:36 +02:00
2018-09-17 08:52:54 -06:00
JSONArray body = new JSONArray ( ) ;
2018-08-15 17:12:36 +02:00
2018-09-17 08:52:54 -06:00
for ( SMSHelper . Message message : messages ) {
2018-08-15 17:12:36 +02:00
try {
JSONObject json = message . toJSONObject ( ) ;
2018-09-17 08:52:54 -06:00
body . put ( json ) ;
2018-08-15 17:12:36 +02:00
} catch ( JSONException e ) {
2019-07-19 17:46:54 +00:00
Log . e ( " Conversations " , " Error serializing message " , e ) ;
2018-08-15 17:12:36 +02:00
}
}
2018-09-17 08:52:54 -06:00
reply . set ( " messages " , body ) ;
2019-07-19 17:46:54 +00:00
reply . set ( " version " , SMS_MESSAGE_PACKET_VERSION ) ;
2018-09-17 08:52:54 -06:00
return reply ;
}
/ * *
* Respond to a request for all conversations
* < p >
* Send one packet of type PACKET_TYPE_SMS_MESSAGE with the first message in all conversations
* /
2020-09-19 05:20:32 +00:00
private boolean handleRequestAllConversations ( NetworkPacket packet ) {
2018-09-17 08:52:54 -06:00
Map < SMSHelper . ThreadID , SMSHelper . Message > conversations = SMSHelper . getConversations ( this . context ) ;
// Prepare the mostRecentTimestamp counter based on these messages, since they are the most
// recent in every conversation
mostRecentTimestampLock . lock ( ) ;
for ( SMSHelper . Message message : conversations . values ( ) ) {
2019-04-19 23:47:18 +02:00
if ( message . date > mostRecentTimestamp ) {
mostRecentTimestamp = message . date ;
2018-09-17 08:52:54 -06:00
}
}
mostRecentTimestampLock . unlock ( ) ;
NetworkPacket reply = constructBulkMessagePacket ( conversations . values ( ) ) ;
2018-08-15 17:12:36 +02:00
device . sendPacket ( reply ) ;
return true ;
}
2020-09-19 05:20:32 +00:00
private boolean handleRequestSingleConversation ( NetworkPacket packet ) {
2018-12-11 18:02:39 -07:00
SMSHelper . ThreadID threadID = new SMSHelper . ThreadID ( packet . getLong ( " threadID " ) ) ;
2018-08-15 17:12:36 +02:00
2022-01-24 01:38:02 +01:00
long rangeStartTimestamp = packet . getLong ( " rangeStartTimestamp " , - 1 ) ;
2020-03-11 00:34:31 +00:00
Long numberToGet = packet . getLong ( " numberToRequest " , - 1 ) ;
if ( numberToGet < 0 ) {
numberToGet = null ;
}
List < SMSHelper . Message > conversation ;
if ( rangeStartTimestamp < 0 ) {
conversation = SMSHelper . getMessagesInThread ( this . context , threadID , numberToGet ) ;
} else {
2020-11-01 23:47:03 +00:00
conversation = SMSHelper . getMessagesInRange ( this . context , threadID , rangeStartTimestamp , numberToGet , true ) ;
2020-03-11 00:34:31 +00:00
}
2018-08-15 17:12:36 +02:00
2020-07-05 13:32:44 +05:30
// Sometimes when desktop app is kept open while android app is restarted for any reason
// mostRecentTimeStamp must be updated in that scenario too if a user request for a
// single conversation and not the entire conversation list
mostRecentTimestampLock . lock ( ) ;
for ( SMSHelper . Message message : conversation ) {
if ( message . date > mostRecentTimestamp ) {
mostRecentTimestamp = message . date ;
}
}
mostRecentTimestampLock . unlock ( ) ;
2018-09-17 08:52:54 -06:00
NetworkPacket reply = constructBulkMessagePacket ( conversation ) ;
2018-08-15 17:12:36 +02:00
device . sendPacket ( reply ) ;
return true ;
}
private boolean isNumberBlocked ( String number ) {
SharedPreferences sharedPref = PreferenceManager . getDefaultSharedPreferences ( context ) ;
String [ ] blockedNumbers = sharedPref . getString ( KEY_PREF_BLOCKED_NUMBERS , " " ) . split ( " \ n " ) ;
for ( String s : blockedNumbers ) {
if ( PhoneNumberUtils . compare ( number , s ) )
return true ;
}
return false ;
}
2020-07-05 13:32:44 +05:30
@Override
public boolean hasSettings ( ) {
return true ;
}
2018-08-15 17:12:36 +02:00
@Override
public String [ ] getSupportedPacketTypes ( ) {
return new String [ ] {
PACKET_TYPE_SMS_REQUEST ,
TelephonyPlugin . PACKET_TYPE_TELEPHONY_REQUEST ,
2018-09-16 16:05:01 -06:00
PACKET_TYPE_SMS_REQUEST_CONVERSATIONS ,
2020-08-31 14:39:57 +05:30
PACKET_TYPE_SMS_REQUEST_CONVERSATION ,
PACKET_TYPE_SMS_REQUEST_ATTACHMENT
2018-08-15 17:12:36 +02:00
} ;
}
@Override
public String [ ] getOutgoingPacketTypes ( ) {
2020-08-31 14:39:57 +05:30
return new String [ ] {
PACKET_TYPE_SMS_MESSAGE ,
PACKET_TYPE_SMS_ATTACHMENT_FILE
} ;
2018-08-15 17:12:36 +02:00
}
@Override
public String [ ] getRequiredPermissions ( ) {
2019-03-21 21:10:22 -06:00
return new String [ ] {
Manifest . permission . SEND_SMS ,
Manifest . permission . READ_SMS ,
2019-07-19 17:46:54 +00:00
// READ_PHONE_STATE should be optional, since we can just query the user, but that
// requires a GUI implementation for querying the user!
Manifest . permission . READ_PHONE_STATE ,
2019-03-21 21:10:22 -06:00
} ;
2018-08-15 17:12:36 +02:00
}
2019-04-19 23:55:46 +02:00
2020-07-05 13:32:44 +05:30
/ * *
* Permissions required for sending and receiving MMs messages
* /
public static String [ ] getMmsPermissions ( ) {
return new String [ ] {
Manifest . permission . RECEIVE_SMS ,
Manifest . permission . RECEIVE_MMS ,
Manifest . permission . WRITE_EXTERNAL_STORAGE ,
Manifest . permission . CHANGE_NETWORK_STATE ,
Manifest . permission . WAKE_LOCK ,
} ;
}
2019-06-05 22:13:59 -06:00
/ * *
2019-07-19 17:46:54 +00:00
* With versions older than KITKAT , lots of the content providers used in SMSHelper become
* un - documented . Most manufacturers * did * do things the same way as was done in mainline
* Android at that time , but some did not . If the manufacturer followed the default route ,
* everything will be fine . If not , the plugin will crash . But , since we have a global catch - all
* in Device . onPacketReceived , it will not crash catastrophically .
* The onCreated method of this SMSPlugin complains if a version older than KitKat is loaded ,
* but it still allowed in the optimistic hope that things will " just work "
2019-06-05 22:13:59 -06:00
* /
2019-04-19 23:55:46 +02:00
@Override
public int getMinSdk ( ) {
2019-07-19 17:46:54 +00:00
return Build . VERSION_CODES . FROYO ;
2019-04-19 23:55:46 +02:00
}
2018-08-15 17:12:36 +02:00
}