2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-09-05 16:45:08 +00:00

[Android] New-style Message received handling

Summary:
Mark old-style SMS packet sending as deprecated (but still supported for backwards-compatibility with old Desktop apps)

Implement a ContentObserver on the Messages database, then send an update to the remote for all incoming or outgoing messages

Test Plan:
1. DBus
  - Connect Android to desktop
  - Subscribe to /modules/kdeconnect/devices/<device_id>/org.kdeconnect.device.conversations/conversationUpdated
  - Receive a new message (text yourself?)
  - Verify that something comes back on dbus (This endpoint is updated by D15409. With that patch you should see the new Message object, before you should just get the (meaningless) conversation ID)

2. SMS App
  - Relies on D15409 - See instructions there

Reviewers: #kde_connect, nicolasfella

Reviewed By: #kde_connect, nicolasfella

Subscribers: nicolasfella, kdeconnect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D15360
This commit is contained in:
Simon Redman
2018-09-17 08:52:54 -06:00
parent 0cc3639aa1
commit df42f992c8
2 changed files with 233 additions and 41 deletions

View File

@@ -27,7 +27,10 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.telephony.PhoneNumberUtils;
@@ -47,8 +50,11 @@ import org.kde.kdeconnect_tp.BuildConfig;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin.PACKET_TYPE_TELEPHONY;
@@ -130,13 +136,89 @@ public class SMSPlugin extends Plugin {
messages.add(SmsMessage.createFromPdu((byte[]) pdu));
}
smsBroadcastReceived(messages);
smsBroadcastReceivedDeprecated(messages);
}
}
};
private void smsBroadcastReceived(ArrayList<SmsMessage> messages) {
/**
* 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
private Lock mostRecentTimestampLock = new ReentrantLock();
private class MessageContentObserver extends ContentObserver {
SMSPlugin mPlugin;
/**
* Create a ContentObserver to watch the Messages database. onChange is called for
* every subscribed change
*
* @param parent Plugin which owns this observer
* @param handler Handler object used to make the callback
*/
public MessageContentObserver(SMSPlugin parent, Handler handler) {
super(handler);
mPlugin = parent;
}
@Override
/**
* 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
*/
public void onChange(boolean selfChange) {
if (mPlugin.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
return;
}
mostRecentTimestampLock.lock();
// Grab the mostRecentTimestamp into the local stack because reading the Messages
// database could potentially be a long operation
long mostRecentTimestamp = mPlugin.mostRecentTimestamp;
mostRecentTimestampLock.unlock();
List<SMSHelper.Message> messages = SMSHelper.getMessagesSinceTimestamp(mPlugin.context, mostRecentTimestamp);
if (messages.size() == 0) {
// Our onChange often gets called many times for a single message. Don't make unnecessary
// noise
return;
}
// Update the most recent counter
mostRecentTimestampLock.lock();
for (SMSHelper.Message message : messages) {
if (message.m_date > mostRecentTimestamp) {
mPlugin.mostRecentTimestamp = message.m_date;
}
}
mostRecentTimestampLock.unlock();
// Send the alert about the update
device.sendPacket(constructBulkMessagePacket(messages));
}
}
@Deprecated
/**
* 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
*/
private void smsBroadcastReceivedDeprecated(ArrayList<SmsMessage> messages) {
if (BuildConfig.DEBUG) {
if (!(messages.size() > 0)) {
@@ -189,6 +271,10 @@ public class SMSPlugin extends Plugin {
filter.setPriority(500);
context.registerReceiver(receiver, filter);
Looper helperLooper = SMSHelper.MessageLooper.getLooper();
ContentObserver messageObserver = new MessageContentObserver(this, new Handler(helperLooper));
SMSHelper.registerObserver(messageObserver, context);
return true;
}
@@ -239,6 +325,35 @@ public class SMSPlugin extends Plugin {
return true;
}
/**
* 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
*/
public static NetworkPacket constructBulkMessagePacket(Collection<SMSHelper.Message> messages) {
NetworkPacket reply = new NetworkPacket(PACKET_TYPE_SMS_MESSAGE);
JSONArray body = new JSONArray();
for (SMSHelper.Message message : messages) {
try {
JSONObject json = message.toJSONObject();
json.put("event", "sms");
body.put(json);
} catch (JSONException e) {
Log.e("Conversations", "Error serializing message");
}
}
reply.set("messages", body);
reply.set("event", "batch_messages");
return reply;
}
/**
* Respond to a request for all conversations
* <p>
@@ -247,24 +362,17 @@ public class SMSPlugin extends Plugin {
private boolean handleRequestConversations(NetworkPacket packet) {
Map<SMSHelper.ThreadID, SMSHelper.Message> conversations = SMSHelper.getConversations(this.context);
NetworkPacket reply = new NetworkPacket(PACKET_TYPE_SMS_MESSAGE);
JSONArray messages = new JSONArray();
// 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()) {
try {
JSONObject json = message.toJSONObject();
json.put("event", "sms");
messages.put(json);
} catch (JSONException e) {
Log.e("Conversations", "Error serializing message");
if (message.m_date > mostRecentTimestamp) {
mostRecentTimestamp = message.m_date;
}
}
mostRecentTimestampLock.unlock();
reply.set("messages", messages);
reply.set("event", "batch_messages"); // Not really necessary, since this is implied by PACKET_TYPE_SMS_MESSAGE, but good for readability
NetworkPacket reply = constructBulkMessagePacket(conversations.values());
device.sendPacket(reply);
@@ -276,24 +384,7 @@ public class SMSPlugin extends Plugin {
List<SMSHelper.Message> conversation = SMSHelper.getMessagesInThread(this.context, threadID);
NetworkPacket reply = new NetworkPacket(PACKET_TYPE_SMS_MESSAGE);
JSONArray messages = new JSONArray();
for (SMSHelper.Message message : conversation) {
try {
JSONObject json = message.toJSONObject();
json.put("event", "sms");
messages.put(json);
} catch (JSONException e) {
Log.e("Conversations", "Error serializing message");
}
}
reply.set("messages", messages);
reply.set("event", "batch_messages");
NetworkPacket reply = constructBulkMessagePacket(conversation);
device.sendPacket(reply);