2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-24 19:07:19 +00:00
2023-03-17 18:57:02 +01:00

231 lines
8.1 KiB
Java

/*
* 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 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_tp.R;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@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 String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_contacts);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_contacts_desc);
}
@Override
public String[] getSupportedPacketTypes() {
return new String[]{
PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMPS,
PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS
};
}
@Override
public String[] getOutgoingPacketTypes() {
return new String[]{
PACKET_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS,
PACKET_TYPE_CONTACTS_RESPONSE_VCARDS
};
}
@Override
public boolean onCreate() {
permissionExplanation = R.string.contacts_permission_explanation;
return true;
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@Override
public String[] getRequiredPermissions() {
return new String[]{Manifest.permission.READ_CONTACTS};
// One day maybe we will also support WRITE_CONTACTS, but not yet
}
/**
* 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-" + device.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);
device.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);
device.sendPacket(reply);
return true;
}
@Override
public boolean onPacketReceived(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;
}
}
}