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

Compare commits

..

9 Commits

Author SHA1 Message Date
Albert Vaca Cintora
df72918b45 Fix keylistener losing the focus
When pressing down on Android, the focus would move from the keylistener
to the buttons below, which made us not be able to listen to other keys.
2025-10-19 11:46:02 +02:00
Albert Vaca Cintora
78741da05a Log a warning when duplicate devices are found 2025-10-18 20:14:30 +02:00
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
16 changed files with 302 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

@@ -38,6 +38,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:id="@+id/keyListener"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusedByDefault="true"
android:layout_above="@id/mouse_buttons"
android:contentDescription="@string/mousepad_info_no_gestures"
/>

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,7 +614,6 @@ 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,
@@ -614,9 +628,6 @@ object SMSHelper {
fileName
)
)
thumbnailImage.recycle()
if (!image.isRecycled) image.recycle()
}
} else if (MimeType.isTypeVideo(contentType)) {
val fileName = data.substring(data.lastIndexOf('/') + 1)
@@ -627,9 +638,8 @@ object SMSHelper {
ContentUris.withAppendedId(mMSPartUri, partID)
)
val videoThumbnail = retriever.frameAtTime
if (videoThumbnail != null) {
val encodedThumbnail = SmsMmsUtils.bitMapToBase64(
videoThumbnail.scale(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
videoThumbnail!!.scale(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
)
attachments.add(
Attachment(
@@ -639,8 +649,6 @@ object SMSHelper {
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

@@ -98,6 +98,17 @@ public class KeyListenerView extends View {
plugin.sendPacket(np);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// consume events that otherwise would move the focus away from us
return keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
keyCode == KeyEvent.KEYCODE_DPAD_UP ||
keyCode == KeyEvent.KEYCODE_DPAD_LEFT ||
keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER ||
keyCode == KeyEvent.KEYCODE_ENTER;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {

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,
};
}

View File

@@ -13,6 +13,7 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
@@ -205,6 +206,8 @@ class PairingFragment : BaseFragment<DevicesListBinding>() {
for (device in moreDevices) {
if (seenNames.contains(device.name)) {
binding.devicesList.addHeaderView(duplicateNamesHeader)
Log.w("PairingFragment", "Duplicate device name detected: ${device.name}")
Log.w("PairingFragment", "Devices:" + moreDevices.toList().toString())
break
}
seenNames.add(device.name);