2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-10-17 14:19:33 +00:00

Compare commits

..

7 Commits

Author SHA1 Message Date
l10n daemon script
27172a8aef GIT_SILENT Sync po/docbooks with svn 2025-10-17 03:03:30 +00:00
l10n daemon script
5cd400e5b1 GIT_SILENT made messages (after extraction) 2025-10-17 02:32:52 +00:00
TPJ Schikhof
15bfa52304 Remove "X-KDECONNECT-TIMESTAMP" https://invent.kde.org/network/kdeconnect-android/-/merge_requests/509#note_1304497 2025-10-16 20:41:41 +02:00
TPJ Schikhof
a3a1e797d4 Fixed issue caused by conflict resolution and deduplicated stored UID's 2025-10-16 20:37:19 +02:00
TPJ Schikhof
2d8b43d75d Migrate ContactsPlugin to Kotlin (conflict resolution) 2025-10-16 20:29:00 +02:00
TPJ Schikhof
3fdc2b4951 Rename .java to .kt 2025-10-16 20:28:07 +02:00
Albert Vaca Cintora
3d7de57336 Remove packet type deprecated in 2018 2025-10-16 12:58:07 +02:00
13 changed files with 287 additions and 365 deletions

View File

@@ -3,7 +3,7 @@
# This file is distributed under the license LGPL version 2.1 or
# version 3 or later versions approved by the membership of KDE e.V.
#
# Josep M. Ferrer <txemaq@gmail.com>, 2023, 2024.
# SPDX-FileCopyrightText: 2023, 2024 Josep M. Ferrer <txemaq@gmail.com>
#. extracted from ./metadata/android/en-US/full_description.txt
msgid ""
msgstr ""

View File

@@ -3,7 +3,7 @@
# This file is distributed under the license LGPL version 2.1 or
# version 3 or later versions approved by the membership of KDE e.V.
#
# Josep M. Ferrer <txemaq@gmail.com>, 2023.
# SPDX-FileCopyrightText: 2023 Josep M. Ferrer <txemaq@gmail.com>
#. extracted from ./metadata/android/en-US/short_description.txt
msgid ""
msgstr ""

View File

@@ -3,7 +3,7 @@
# This file is distributed under the license LGPL version 2.1 or
# version 3 or later versions approved by the membership of KDE e.V.
#
# Josep M. Ferrer <txemaq@gmail.com>, 2023, 2024.
# SPDX-FileCopyrightText: 2023, 2024 Josep M. Ferrer <txemaq@gmail.com>
#. extracted from ./metadata/android/en-US/full_description.txt
msgid ""
msgstr ""

View File

@@ -3,7 +3,7 @@
# This file is distributed under the license LGPL version 2.1 or
# version 3 or later versions approved by the membership of KDE e.V.
#
# Josep M. Ferrer <txemaq@gmail.com>, 2023.
# SPDX-FileCopyrightText: 2023 Josep M. Ferrer <txemaq@gmail.com>
#. extracted from ./metadata/android/en-US/short_description.txt
msgid ""
msgstr ""

View File

@@ -240,6 +240,7 @@
<string name="custom_devices_settings_summary">%d أجهزة مضافة يدوياً</string>
<string name="custom_device_list">أضف أجهزة بعنوان IP</string>
<string name="custom_device_deleted">حُذف الجهاز المخصّص</string>
<string name="custom_device_list_help">إذا لم يُكتشف جهازك آليّاً يمكنك إضافة عنوان IP الخاص به أو اسم المضيف الخاص به من خلال النقر على الزر في الأسفل</string>
<string name="custom_device_fab_hint">أضف جهازاً</string>
<string name="undo">تراجع</string>
<string name="share_notification_preference">إشعارات مزعجة</string>

View File

@@ -208,6 +208,7 @@
<string name="custom_devices_settings_summary">%d dispositivi aggiunti manualmente</string>
<string name="custom_device_list">Aggiungi dispositivi per IP</string>
<string name="custom_device_deleted">Dispositivo personalizzato eliminato</string>
<string name="custom_device_list_help">Se il tuo dispositivo non è rilevato automaticamente, puoi aggiungere il suo indirizzo IP o il nome host facendo clic sul pulsante seguente</string>
<string name="custom_device_fab_hint">Aggiungi un dispositivo</string>
<string name="undo">Annulla</string>
<string name="share_notification_preference">Notifiche rumorose</string>

View File

@@ -65,6 +65,8 @@
<string name="mousepad_scroll_sensitivity_title">רגישות גלילה</string>
<string name="gyro_mouse_enabled_title">הפעלת עכבר גירוסקופי</string>
<string name="gyro_mouse_sensitivity_title">רגישות גירוסקופ</string>
<string name="bigscreen_show_home_title">הצגת כפתור הבית</string>
<string name="bigscreen_show_back_title">הצגת כפתור חזרה</string>
<string-array name="mousepad_tap_entries">
<item>לחיצה שמאלית</item>
<item>לחיצה ימנית</item>
@@ -222,6 +224,7 @@
<string name="custom_devices_settings_summary">%d התקנים נוספו ידנית</string>
<string name="custom_device_list">הוספת מכשירים לפי IP</string>
<string name="custom_device_deleted">מכשיר מותאם אישית נמחק</string>
<string name="custom_device_list_help">אם המכשיר שלך לא מזוהה אוטומטית אפשר להוסיף את כתובת ה־IP או את שם המארח שלו בלחיצה על הכפתור שלהלן</string>
<string name="custom_device_fab_hint">הוספת מכשיר</string>
<string name="undo">החזרה</string>
<string name="share_notification_preference">התראות רועשות</string>
@@ -362,6 +365,7 @@
<string name="bigscreen_select">בחירה</string>
<string name="bigscreen_right">ימין</string>
<string name="bigscreen_down">למטה</string>
<string name="bigscreen_back">חזרה</string>
<string name="bigscreen_mic">מיקרופון</string>
<string name="pref_plugin_bigscreen">שלט Bigscreen</string>
<string name="pref_plugin_bigscreen_desc">אפשר להשתמש במכשיר שלך כשלט ל־Bigscreen פלזמה</string>

View File

@@ -53,7 +53,21 @@ object SMSHelper {
// The constant Telephony.Mms.Part.CONTENT_URI was added in API 29
val mMSPartUri : Uri = "content://mms/part/".toUri()
val mConversationUri : Uri = "content://mms-sms/conversations?simple=true".toUri()
/**
* Get the base address for all message conversations
* We only use this to fetch thread_ids because the data it returns if often incomplete or useless
*/
private fun getConversationUri(): Uri {
// Special case for Samsung
// For some reason, Samsung devices do not support the regular SmsMms column.
// However, according to https://stackoverflow.com/a/13640868/3723163, we can work around it this way.
// By my understanding, "simple=true" means we can't support multi-target messages.
// Go complain to Samsung about their annoying OS changes!
if ("Samsung".equals(Build.MANUFACTURER, ignoreCase = true)) {
Log.i("SMSHelper", "This appears to be a Samsung device. This may cause some features to not work properly.")
}
return "content://mms-sms/conversations?simple=true".toUri()
}
private fun getCompleteConversationsUri(): Uri {
// This glorious - but completely undocumented - content URI gives us all messages, both MMS and SMS,
@@ -362,7 +376,7 @@ object SMSHelper {
if (getSubscriptionIdSupport(uri, context)) {
allColumns.addAll(Message.multiSIMColumns)
}
if (uri != mConversationUri) {
if (uri != getConversationUri()) {
// See https://issuetracker.google.com/issues/134592631
allColumns.add(TRANSPORT_TYPE_DISCRIMINATOR_COLUMN)
}
@@ -414,6 +428,7 @@ object SMSHelper {
* @return Non-blocking iterable of the first message in each conversation
*/
fun getConversations(context: Context): Sequence<Message> {
val uri = getConversationUri()
// Used to avoid spewing logs in case there is an overall problem with fetching thread IDs
var warnedForNullThreadIDs = false
@@ -428,7 +443,7 @@ object SMSHelper {
// return conversations, but I doubt anyone will ever find it necessary.
var threadIds: List<ThreadID>
context.contentResolver.query(
mConversationUri,
uri,
null,
null,
null,
@@ -599,24 +614,20 @@ object SMSHelper {
// Get the actual image from the mms database convert it into thumbnail and encode to Base64
val image = SmsMmsUtils.getMmsImage(context, partID)
if (image != null) {
val thumbnailImage = ThumbnailUtils.extractThumbnail(
image,
THUMBNAIL_WIDTH,
THUMBNAIL_HEIGHT
val thumbnailImage = ThumbnailUtils.extractThumbnail(
image,
THUMBNAIL_WIDTH,
THUMBNAIL_HEIGHT
)
val encodedThumbnail = SmsMmsUtils.bitMapToBase64(thumbnailImage)
attachments.add(
Attachment(
partID,
contentType,
encodedThumbnail,
fileName
)
val encodedThumbnail = SmsMmsUtils.bitMapToBase64(thumbnailImage)
attachments.add(
Attachment(
partID,
contentType,
encodedThumbnail,
fileName
)
)
thumbnailImage.recycle()
if (!image.isRecycled) image.recycle()
}
)
} else if (MimeType.isTypeVideo(contentType)) {
val fileName = data.substring(data.lastIndexOf('/') + 1)
@@ -627,20 +638,17 @@ object SMSHelper {
ContentUris.withAppendedId(mMSPartUri, partID)
)
val videoThumbnail = retriever.frameAtTime
if (videoThumbnail != null) {
val encodedThumbnail = SmsMmsUtils.bitMapToBase64(
videoThumbnail.scale(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
val encodedThumbnail = SmsMmsUtils.bitMapToBase64(
videoThumbnail!!.scale(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
)
attachments.add(
Attachment(
partID,
contentType,
encodedThumbnail,
fileName
)
attachments.add(
Attachment(
partID,
contentType,
encodedThumbnail,
fileName
)
)
}
retriever.release()
)
} else if (MimeType.isTypeAudio(contentType)) {
val fileName = data.substring(data.lastIndexOf('/') + 1)
attachments.add(Attachment(partID, contentType, null, fileName))
@@ -763,6 +771,15 @@ object SMSHelper {
return body
}
/**
* Register a ContentObserver for the Messages database
*
* @param observer ContentObserver to alert on Message changes
*/
fun registerObserver(observer: ContentObserver, context: Context) {
context.contentResolver.registerContentObserver(getConversationUri(), true, observer)
}
/**
* Converts a given JSONArray of attachments into List<Attachment>
*
@@ -864,7 +881,7 @@ object SMSHelper {
}
}
class Address(context: Context, private val address: String) {
class Address(val context: Context, private val address: String) {
@Throws(JSONException::class)
fun toJson(): JSONObject {
val json = JSONObject()

View File

@@ -202,6 +202,10 @@ class NetworkPacket private constructor(
return mBody.has(key)
}
operator fun contains(key: String): Boolean {
return has(key)
}
@Throws(JSONException::class)
fun serialize(): String {
val jo = JSONObject()

View File

@@ -1,262 +0,0 @@
/*
* ContactsPlugin.java - This file is part of KDE Connect's Android App
* Implement a way to request and send contact information
*
* SPDX-FileCopyrightText: 2018 Simon Redman <simon@ergotech.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.Plugins.ContactsPlugin;
import android.Manifest;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import org.kde.kdeconnect.Helpers.ContactsHelper;
import org.kde.kdeconnect.Helpers.ContactsHelper.ContactNotFoundException;
import org.kde.kdeconnect.Helpers.ContactsHelper.VCardBuilder;
import org.kde.kdeconnect.Helpers.ContactsHelper.uID;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.AlertDialogFragment;
import org.kde.kdeconnect_tp.R;
import java.util.*;
@PluginFactory.LoadablePlugin
public class ContactsPlugin extends Plugin {
/**
* Used to request the device send the unique ID of every contact
*/
private static final String PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMPS = "kdeconnect.contacts.request_all_uids_timestamps";
/**
* Used to request the names for the contacts corresponding to a list of UIDs
* <p>
* It shall contain the key "uids", which will have a list of uIDs (long int, as string)
*/
private static final String PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS = "kdeconnect.contacts.request_vcards_by_uid";
/**
* Response indicating the packet contains a list of contact uIDs
* <p>
* It shall contain the key "uids", which will mark a list of uIDs (long int, as string)
* The returned IDs can be used in future requests for more information about the contact
*/
private static final String PACKET_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS = "kdeconnect.contacts.response_uids_timestamps";
/**
* Response indicating the packet contains a list of contact names
* <p>
* It shall contain the key "uids", which will mark a list of uIDs (long int, as string)
* then, for each UID, there shall be a field with the key of that UID and the value of the name of the contact
* <p>
* For example:
* { 'uids' : ['1', '3', '15'],
* '1' : 'John Smith',
* '3' : 'Abe Lincoln',
* '15' : 'Mom'
* }
*/
private static final String PACKET_TYPE_CONTACTS_RESPONSE_VCARDS = "kdeconnect.contacts.response_vcards";
@Override
public @NonNull String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_contacts);
}
@Override
public @NonNull String getDescription() {
return context.getResources().getString(R.string.pref_plugin_contacts_desc);
}
@Override
public @NonNull String[] getSupportedPacketTypes() {
return new String[]{
PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMPS,
PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS
};
}
@Override
public @NonNull String[] getOutgoingPacketTypes() {
return new String[]{
PACKET_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS,
PACKET_TYPE_CONTACTS_RESPONSE_VCARDS
};
}
@Override
protected int getPermissionExplanation() {
return R.string.contacts_permission_explanation;
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@Override
public @NonNull String[] getRequiredPermissions() {
return new String[]{Manifest.permission.READ_CONTACTS};
// One day maybe we will also support WRITE_CONTACTS, but not yet
}
@Override
public boolean checkRequiredPermissions() {
if (!arePermissionsGranted(getRequiredPermissions())) {
return false;
}
return getPreferences().getBoolean("acceptedToTransferContacts", false);
}
@Override
public boolean supportsDeviceSpecificSettings() {
return true;
}
public @NonNull DialogFragment getPermissionExplanationDialog() {
if (!arePermissionsGranted(getRequiredPermissions())) {
return super.getPermissionExplanationDialog();
}
AlertDialogFragment dialog = new AlertDialogFragment.Builder()
.setTitle(getDisplayName())
.setMessage(R.string.contacts_per_device_confirmation)
.setPositiveButton(R.string.ok)
.setNegativeButton(R.string.cancel)
.create();
dialog.setCallback(new AlertDialogFragment.Callback() {
@Override
public boolean onPositiveButtonClicked() {
Objects.requireNonNull(getPreferences()).edit().putBoolean("acceptedToTransferContacts", true).apply();
Objects.requireNonNull(getDevice()).launchBackgroundReloadPluginsFromSettings();
return true;
}
});
return dialog;
}
/**
* Add custom fields to the vcard to keep track of KDE Connect-specific fields
* <p>
* These include the local device's uID as well as last-changed timestamp
* <p>
* This might be extended in the future to include more fields
*
* @param vcard vcard to apply metadata to
* @param uID uID to which the vcard corresponds
* @throws ContactNotFoundException If the given ID for some reason does not match a contact
* @return The same VCard as was passed in, but now with KDE Connect-specific fields
*/
private VCardBuilder addVCardMetadata(VCardBuilder vcard, uID uID) throws ContactNotFoundException {
// Append the device ID line
// Unclear if the deviceID forms a valid name per the vcard spec. Worry about that later..
vcard.appendLine("X-KDECONNECT-ID-DEV-" + getDevice().getDeviceId(),
uID.toString());
// Build the timestamp line
// Maybe one day this should be changed into the vcard-standard REV key
Long timestamp = ContactsHelper.getContactTimestamp(context, uID);
vcard.appendLine("X-KDECONNECT-TIMESTAMP",
timestamp.toString());
return vcard;
}
/**
* Return a unique identifier (Contacts.LOOKUP_KEY) for all contacts in the Contacts database
* <p>
* The identifiers returned can be used in future requests to get more information
* about the contact
*
* @param np The packet containing the request
* @return true if successfully handled, false otherwise
*/
@SuppressWarnings("SameReturnValue")
private boolean handleRequestAllUIDsTimestamps(@SuppressWarnings("unused") NetworkPacket np) {
NetworkPacket reply = new NetworkPacket(PACKET_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS);
Map<uID, Long> uIDsToTimestamps = ContactsHelper.getAllContactTimestamps(context);
int contactCount = uIDsToTimestamps.size();
List<String> uIDs = new ArrayList<>(contactCount);
for (uID contactID : uIDsToTimestamps.keySet()) {
Long timestamp = uIDsToTimestamps.get(contactID);
reply.set(contactID.toString(), timestamp);
uIDs.add(contactID.toString());
}
reply.set("uids", uIDs);
getDevice().sendPacket(reply);
return true;
}
private boolean handleRequestVCardsByUIDs(NetworkPacket np) {
if (!np.has("uids")) {
Log.e("ContactsPlugin", "handleRequestNamesByUIDs received a malformed packet with no uids key");
return false;
}
List<String> uIDsAsStrings = np.getStringList("uids");
// Convert to Collection<uIDs> to call getVCardsForContactIDs
Set<uID> uIDs = new HashSet<>(uIDsAsStrings.size());
for (String uID : uIDsAsStrings) {
uIDs.add(new uID(uID));
}
Map<uID, VCardBuilder> uIDsToVCards = ContactsHelper.getVCardsForContactIDs(context, uIDs);
// ContactsHelper.getVCardsForContactIDs(..) is allowed to reply without
// some of the requested uIDs if they were not in the database, so update our list
uIDsAsStrings = new ArrayList<>(uIDsToVCards.size());
NetworkPacket reply = new NetworkPacket(PACKET_TYPE_CONTACTS_RESPONSE_VCARDS);
// Add the vcards to the packet
for (uID uID : uIDsToVCards.keySet()) {
VCardBuilder vcard = uIDsToVCards.get(uID);
try {
vcard = this.addVCardMetadata(vcard, uID);
// Store this as a valid uID
uIDsAsStrings.add(uID.toString());
// Add the uid -> vcard pairing to the packet
reply.set(uID.toString(), vcard.toString());
} catch (ContactsHelper.ContactNotFoundException e) {
e.printStackTrace();
}
}
// Add the valid uIDs to the packet
reply.set("uids", uIDsAsStrings);
getDevice().sendPacket(reply);
return true;
}
@Override
public boolean onPacketReceived(@NonNull NetworkPacket np) {
switch (np.getType()) {
case PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMPS:
return this.handleRequestAllUIDsTimestamps(np);
case PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS:
return this.handleRequestVCardsByUIDs(np);
default:
Log.e("ContactsPlugin", "Contacts plugin received an unexpected packet!");
return false;
}
}
}

View File

@@ -0,0 +1,218 @@
/*
* ContactsPlugin.java - This file is part of KDE Connect's Android App
* Implement a way to request and send contact information
*
* SPDX-FileCopyrightText: 2018 Simon Redman <simon@ergotech.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.Plugins.ContactsPlugin
import android.Manifest
import android.util.Log
import androidx.fragment.app.DialogFragment
import org.kde.kdeconnect.Helpers.ContactsHelper
import org.kde.kdeconnect.Helpers.ContactsHelper.ContactNotFoundException
import org.kde.kdeconnect.Helpers.ContactsHelper.VCardBuilder
import org.kde.kdeconnect.Helpers.ContactsHelper.uID
import org.kde.kdeconnect.NetworkPacket
import org.kde.kdeconnect.Plugins.Plugin
import org.kde.kdeconnect.Plugins.PluginFactory.LoadablePlugin
import org.kde.kdeconnect.UserInterface.AlertDialogFragment
import org.kde.kdeconnect_tp.R
import androidx.core.content.edit
@LoadablePlugin
class ContactsPlugin : Plugin() {
override val displayName: String
get() = context.resources.getString(R.string.pref_plugin_contacts)
override val description: String
get() = context.resources.getString(R.string.pref_plugin_contacts_desc)
override val supportedPacketTypes: Array<String>
get() = arrayOf(PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMPS, PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS)
override val outgoingPacketTypes: Array<String>
get() = arrayOf(PACKET_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS, PACKET_TYPE_CONTACTS_RESPONSE_VCARDS)
override val permissionExplanation: Int
get() = R.string.contacts_permission_explanation
override val isEnabledByDefault: Boolean
get() = true
// One day maybe we will also support WRITE_CONTACTS, but not yet
override val requiredPermissions: Array<String>
get() = arrayOf(Manifest.permission.READ_CONTACTS)
override fun checkRequiredPermissions(): Boolean {
if (!arePermissionsGranted(requiredPermissions)) {
return false
}
return preferences!!.getBoolean("acceptedToTransferContacts", false)
}
override fun supportsDeviceSpecificSettings(): Boolean = true
override val permissionExplanationDialog: DialogFragment
get() {
if (!arePermissionsGranted(requiredPermissions)) {
return super.permissionExplanationDialog
}
return AlertDialogFragment.Builder()
.setTitle(displayName)
.setMessage(R.string.contacts_per_device_confirmation)
.setPositiveButton(R.string.ok)
.setNegativeButton(R.string.cancel)
.create()
.apply {
setCallback(object : AlertDialogFragment.Callback() {
override fun onPositiveButtonClicked(): Boolean {
preferences!!.edit { putBoolean("acceptedToTransferContacts", true) }
device.launchBackgroundReloadPluginsFromSettings()
return true
}
})
}
}
/**
* Add custom fields to the vcard to keep track of KDE Connect-specific fields
*
*
* These include the local device's uID as well as last-changed timestamp
*
*
* This might be extended in the future to include more fields
*
* @param vcard vcard to apply metadata to
* @param uID uID to which the vcard corresponds
* @throws ContactNotFoundException If the given ID for some reason does not match a contact
* @return The same VCard as was passed in, but now with KDE Connect-specific fields
*/
@Throws(ContactNotFoundException::class)
private fun addVCardMetadata(vcard: VCardBuilder, uID: uID): VCardBuilder {
// Append the device ID line
// Unclear if the deviceID forms a valid name per the vcard spec. Worry about that later..
vcard.appendLine("X-KDECONNECT-ID-DEV-${device.deviceId}", uID.toString())
val timestamp: Long = ContactsHelper.getContactTimestamp(context, uID)
vcard.appendLine("REV", timestamp.toString())
return vcard
}
/**
* Return a unique identifier (Contacts.LOOKUP_KEY) for all contacts in the Contacts database
*
*
* The identifiers returned can be used in future requests to get more information about the contact
*
* @param np The packet containing the request
* @return true if successfully handled, false otherwise
*/
private fun handleRequestAllUIDsTimestamps(@Suppress("unused") np: NetworkPacket): Boolean {
val uIDsToTimestamps: Map<uID, Long> = ContactsHelper.getAllContactTimestamps(context)
val reply = NetworkPacket(PACKET_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS).apply {
val uIDsAsString = mutableListOf<String>()
for ((contactID: uID, timestamp: Long) in uIDsToTimestamps) {
set(contactID.toString(), timestamp.toString())
uIDsAsString.add(contactID.toString())
}
set(PACKET_UIDS_KEY, uIDsAsString)
}
device.sendPacket(reply)
return true
}
private fun handleRequestVCardsByUIDs(np: NetworkPacket): Boolean {
if (PACKET_UIDS_KEY !in np) {
Log.e("ContactsPlugin", "handleRequestNamesByUIDs received a malformed packet with no uids key")
return false
}
val storedUIDs: List<uID>? = np.getStringList("uids")?.distinct()?.map { uID(it) }
if (storedUIDs == null) {
Log.e("ContactsPlugin", "handleRequestNamesByUIDs received a malformed packet with no uids")
return false
}
val uIDsToVCards: Map<uID, VCardBuilder> = ContactsHelper.getVCardsForContactIDs(context, storedUIDs)
val reply = NetworkPacket(PACKET_TYPE_CONTACTS_RESPONSE_VCARDS).apply {
// ContactsHelper.getVCardsForContactIDs(..) is allowed to reply without some of the requested uIDs if they were not in the database, so update our list
val uIDsAsStrings = mutableListOf<String>()
for ((uID: uID, vcard: VCardBuilder) in uIDsToVCards) {
try {
val vcardWithMetadata = addVCardMetadata(vcard, uID)
// Store this as a valid uID
uIDsAsStrings.add(uID.toString())
// Add the uid -> vcard pairing to the packet
set(uID.toString(), vcardWithMetadata.toString())
} catch (e: ContactNotFoundException) {
Log.e("ContactsPlugin", "handleRequestVCardsByUIDs failed to find contact with uID $uID")
}
}
set(PACKET_UIDS_KEY, uIDsAsStrings)
}
device.sendPacket(reply)
return true
}
override fun onPacketReceived(np: NetworkPacket): Boolean = when (np.type) {
PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMPS -> this.handleRequestAllUIDsTimestamps(np)
PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS -> this.handleRequestVCardsByUIDs(np)
else -> {
Log.e("ContactsPlugin", "Contacts plugin received an unexpected packet!")
false
}
}
companion object {
private const val PACKET_UIDS_KEY: String = "uids"
/**
* Used to request the device send the unique ID of every contact
*/
private const val PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMPS: String = "kdeconnect.contacts.request_all_uids_timestamps"
/**
* Used to request the names for the contacts corresponding to a list of UIDs
*
*
* It shall contain the key "uids", which will have a list of uIDs (long int, as string)
*/
private const val PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS: String = "kdeconnect.contacts.request_vcards_by_uid"
/**
* Response indicating the packet contains a list of contact uIDs
*
*
* It shall contain the key "uids", which will mark a list of uIDs (long int, as string)
* The returned IDs can be used in future requests for more information about the contact
*/
private const val PACKET_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS: String = "kdeconnect.contacts.response_uids_timestamps"
/**
* Response indicating the packet contains a list of contact names
*
*
* It shall contain the key "uids", which will mark a list of uIDs (long int, as string)
* then, for each UID, there shall be a field with the key of that UID and the value of the name of the contact
*
*
* For example:
* { 'uids' : ['1', '3', '15'],
* '1' : 'John Smith',
* '3' : 'Abe Lincoln',
* '15' : 'Mom'
* }
*/
private const val PACKET_TYPE_CONTACTS_RESPONSE_VCARDS: String = "kdeconnect.contacts.response_vcards"
}
}

View File

@@ -28,7 +28,6 @@ import android.telephony.SmsManager
import android.telephony.SmsMessage
import androidx.annotation.WorkerThread
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import com.klinker.android.logger.Log
import com.klinker.android.send_message.Transaction
import org.json.JSONArray
@@ -44,6 +43,7 @@ import org.kde.kdeconnect.Helpers.SMSHelper.getMessagesInThread
import org.kde.kdeconnect.Helpers.SMSHelper.getNewestMessageTimestamp
import org.kde.kdeconnect.Helpers.SMSHelper.jsonArrayToAddressList
import org.kde.kdeconnect.Helpers.SMSHelper.jsonArrayToAttachmentsList
import org.kde.kdeconnect.Helpers.SMSHelper.registerObserver
import org.kde.kdeconnect.Helpers.ThreadHelper.execute
import org.kde.kdeconnect.NetworkPacket
import org.kde.kdeconnect.Plugins.Plugin
@@ -134,9 +134,6 @@ class SMSPlugin : Plugin() {
}
}
private val messageObserver: ContentObserver = MessageContentObserver(Handler(getLooper()!!))
/**
* Helper method to read the latest message from the sms-mms database and sends it to the desktop
*
@@ -235,7 +232,9 @@ class SMSPlugin : Plugin() {
refreshFilter.priority = 500
context.registerReceiver(messagesUpdateReceiver, refreshFilter, ContextCompat.RECEIVER_EXPORTED)
context.contentResolver.registerContentObserver(SMSHelper.mConversationUri, true, messageObserver)
val helperLooper: Looper? = getLooper()
val messageObserver: ContentObserver = MessageContentObserver(Handler(helperLooper!!))
registerObserver(messageObserver, context)
// To see debug messages for Klinker library, uncomment the below line
//Log.setDebug(true)
@@ -246,12 +245,6 @@ class SMSPlugin : Plugin() {
return true
}
override fun onDestroy() {
context.unregisterReceiver(receiver)
context.unregisterReceiver(messagesUpdateReceiver)
context.contentResolver.unregisterContentObserver(messageObserver)
}
override val displayName: String
get() = context.resources.getString(R.string.pref_plugin_telepathy)
@@ -288,27 +281,6 @@ class SMSPlugin : Plugin() {
true
}
TelephonyPlugin.PACKET_TYPE_TELEPHONY_REQUEST -> {
if (np.getBoolean("sendSms")) {
val phoneNo: String = np.getString("phoneNumber")
val sms: String = np.getString("messageBody")
val subID = np.getLong("subID", -1)
try {
val smsManager: SmsManager = if (subID == -1L) SmsManager.getDefault() else SmsManager.getSmsManagerForSubscriptionId(subID.toInt())
val parts: ArrayList<String> = 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 (e: Exception) {
//TODO: Notify other end
Log.e("SMSPlugin", "Exception", e)
}
}
true
}
PACKET_TYPE_SMS_REQUEST_ATTACHMENT -> {
val partID: Long = np.getLong("part_id")
val uniqueIdentifier: String = np.getString("unique_identifier")
@@ -387,7 +359,6 @@ class SMSPlugin : Plugin() {
override val supportedPacketTypes: Array<String>
get() = arrayOf(
PACKET_TYPE_SMS_REQUEST,
TelephonyPlugin.PACKET_TYPE_TELEPHONY_REQUEST,
PACKET_TYPE_SMS_REQUEST_CONVERSATIONS,
PACKET_TYPE_SMS_REQUEST_CONVERSATION,
PACKET_TYPE_SMS_REQUEST_ATTACHMENT

View File

@@ -55,31 +55,6 @@ public class TelephonyPlugin extends Plugin {
*/
public final static String PACKET_TYPE_TELEPHONY = "kdeconnect.telephony";
/**
* Old-style packet sent to request a simple telephony action
* <p>
* The events handled were:
* - to request the device to mute its ringer
* - to request an SMS to be sent.
* <p>
* In case an SMS was being requested, the body was like so:
* { "sendSms": true,
* "phoneNumber": "542904563213",
* "messageBody": "Hi mom!"
* }
* <p>
* In case a ringer muted was requested, the body looked like so:
* { "action": "mute" }
* <p>
* Ringer mute requests are best handled by PACKET_TYPE_TELEPHONY_REQUEST_MUTE
* <p>
* This packet type is retained for backwards-compatibility with old desktop applications,
* but support should be dropped once those applications are no longer supported. New
* applications should not use this packet type.
*/
@Deprecated
public final static String PACKET_TYPE_TELEPHONY_REQUEST = "kdeconnect.telephony.request";
/**
* Packet sent to indicate the user has requested the device mute its ringer
* <p>
@@ -266,13 +241,7 @@ public class TelephonyPlugin extends Plugin {
@Override
public boolean onPacketReceived(@NonNull NetworkPacket np) {
switch (np.getType()) {
case PACKET_TYPE_TELEPHONY_REQUEST:
if (np.getString("action").equals("mute")) {
muteRinger();
}
break;
case PACKET_TYPE_TELEPHONY_REQUEST_MUTE:
muteRinger();
break;
@@ -295,7 +264,6 @@ public class TelephonyPlugin extends Plugin {
@Override
public @NonNull String[] getSupportedPacketTypes() {
return new String[]{
PACKET_TYPE_TELEPHONY_REQUEST,
PACKET_TYPE_TELEPHONY_REQUEST_MUTE,
};
}