mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-28 12:47:43 +00:00
Refactor contacts-getting code to be either "everything" or "one"
Fixes bug from mailing list conversation dated 12 October 2019
This commit is contained in:
parent
1d5c280401
commit
6f81c67632
@ -33,8 +33,8 @@ import android.util.Base64OutputStream;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.collection.LongSparseArray;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@ -52,6 +52,7 @@ import java.util.Set;
|
||||
|
||||
public class ContactsHelper {
|
||||
|
||||
static final String LOG_TAG = "ContactsHelper";
|
||||
|
||||
/**
|
||||
* Lookup the name and photoID of a contact given a phone number
|
||||
@ -103,7 +104,7 @@ public class ContactsHelper {
|
||||
}
|
||||
return encodedPhoto.toString();
|
||||
} catch (Exception ex) {
|
||||
Log.e("ContactsHelper", ex.toString());
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@ -140,7 +141,7 @@ public class ContactsHelper {
|
||||
} else {
|
||||
// Something went wrong with this contact
|
||||
// If you are experiencing this, please open a bug report indicating how you got here
|
||||
Log.e("ContactsHelper", "Got a contact which does not have a LOOKUP_KEY");
|
||||
Log.e(LOG_TAG, "Got a contact which does not have a LOOKUP_KEY");
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -211,97 +212,110 @@ public class ContactsHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a mapping of contact IDs to a map of the requested data from the Contacts database
|
||||
* <p>
|
||||
* If for some reason there is no row associated with the contact ID in the database,
|
||||
* there will not be a corresponding field in the returned map
|
||||
* Get the last-modified timestamp for every contact in the database
|
||||
*
|
||||
* @param context android.content.Context running the request
|
||||
* @param IDs collection of contact uIDs to look up
|
||||
* @param contactsProjection List of column names to extract, defined in ContactsContract.Contacts
|
||||
* @param context android.content.Context running the request
|
||||
* @return Mapping of contact uID to last-modified timestamp
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) // Need API 18 for contact timestamps
|
||||
public static Map<uID, Long> getAllContactTimestamps(Context context) {
|
||||
String[] projection = { uID.COLUMN, ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP };
|
||||
|
||||
Map<uID, Map<String, String>> databaseValues = accessContactsDatabase(context, projection, null, null, null);
|
||||
|
||||
Map<uID, Long> timestamps = new HashMap<>();
|
||||
for (uID contactID : databaseValues.keySet()) {
|
||||
Map<String, String> data = databaseValues.get(contactID);
|
||||
timestamps.put(
|
||||
contactID,
|
||||
Long.parseLong(data.get(ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP))
|
||||
);
|
||||
}
|
||||
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last-modified timestamp for the specified contact
|
||||
*
|
||||
* @param context android.content.Context running the request
|
||||
* @param contactID Contact uID to read
|
||||
* @throws ContactNotFoundException If the given ID for some reason does not match a contact
|
||||
* @return Last-modified timestamp of the contact
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) // Need API 18 for contact timestamps
|
||||
public static Long getContactTimestamp(Context context, uID contactID) throws ContactNotFoundException {
|
||||
String[] projection = { uID.COLUMN, ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP };
|
||||
String selection = uID.COLUMN + " = ?";
|
||||
String[] selectionArgs = { contactID.toString() };
|
||||
|
||||
Map<uID, Map<String, String>> databaseValue = accessContactsDatabase(context, projection, selection, selectionArgs, null);
|
||||
|
||||
if (databaseValue.size() == 0) {
|
||||
throw new ContactNotFoundException("Querying for contact with id " + contactID + " returned no results.");
|
||||
}
|
||||
|
||||
if (databaseValue.size() != 1) {
|
||||
Log.w(LOG_TAG, "Received an improper number of return values from the database in getContactTimestamp: " + databaseValue.size());
|
||||
}
|
||||
|
||||
Long timestamp = Long.parseLong(databaseValue.get(contactID).get(ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP));
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a mapping of contact IDs to a map of the requested data from the Contacts database.
|
||||
*
|
||||
* @param context android.content.Context running the request
|
||||
* @param projection List of column names to extract, defined in ContactsContract.Contacts. Must contain uID.COLUMN
|
||||
* @param selection Parameterizable filter to use with the ContentResolver query. May be null.
|
||||
* @param selectionArgs Parameters for selection. May be null.
|
||||
* @param sortOrder Sort order to request from the ContentResolver query. May be null.
|
||||
* @return mapping of contact uIDs to desired values, which are a mapping of column names to the data contained there
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB) // Needed for Cursor.getType(..)
|
||||
public static Map<uID, Map<String, Object>> getColumnsFromContactsForIDs(Context context, Collection<uID> IDs, String[] contactsProjection) {
|
||||
HashMap<uID, Map<String, Object>> toReturn = new HashMap<>();
|
||||
|
||||
if (IDs.isEmpty()) {
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private static Map<uID, Map<String, String>> accessContactsDatabase(
|
||||
@NonNull Context context,
|
||||
@NonNull String[] projection,
|
||||
@Nullable String selection,
|
||||
@Nullable String[] selectionArgs,
|
||||
@Nullable String sortOrder
|
||||
) {
|
||||
Uri contactsUri = ContactsContract.Contacts.CONTENT_URI;
|
||||
|
||||
// Regardless of whether it was requested, we need to look up the uID column
|
||||
Set<String> lookupProjection = new HashSet<>(Arrays.asList(contactsProjection));
|
||||
lookupProjection.add(uID.COLUMN);
|
||||
|
||||
// We need a selection which looks like "<column> IN(?,?,...?)" with one ? per ID
|
||||
StringBuilder contactsSelection = new StringBuilder(uID.COLUMN);
|
||||
contactsSelection.append(" IN(");
|
||||
|
||||
for (int i = 0; i < IDs.size(); i++) {
|
||||
contactsSelection.append("?,");
|
||||
}
|
||||
// Remove trailing comma
|
||||
contactsSelection.deleteCharAt(contactsSelection.length() - 1);
|
||||
contactsSelection.append(")");
|
||||
|
||||
// We need selection arguments as simply a String representation of each ID
|
||||
List<String> contactsArgs = new ArrayList<>();
|
||||
for (uID ID : IDs) {
|
||||
contactsArgs.add(ID.toString());
|
||||
}
|
||||
HashMap<uID, Map<String, String>> toReturn = new HashMap<>();
|
||||
|
||||
try (Cursor contactsCursor = context.getContentResolver().query(
|
||||
contactsUri,
|
||||
lookupProjection.toArray(new String[0]),
|
||||
contactsSelection.toString(),
|
||||
contactsArgs.toArray(new String[0]),
|
||||
null
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
sortOrder
|
||||
)) {
|
||||
if (contactsCursor != null && contactsCursor.moveToFirst()) {
|
||||
do {
|
||||
Map<String, Object> requestedData = new HashMap<>();
|
||||
Map<String, String> requestedData = new HashMap<>();
|
||||
|
||||
int lookupKeyIdx = contactsCursor.getColumnIndexOrThrow(uID.COLUMN);
|
||||
String lookupKey = contactsCursor.getString(lookupKeyIdx);
|
||||
int uIDIndex = contactsCursor.getColumnIndexOrThrow(uID.COLUMN);
|
||||
uID uID = new uID(contactsCursor.getString(uIDIndex));
|
||||
|
||||
// For each column, collect the data from that column
|
||||
for (String column : contactsProjection) {
|
||||
for (String column : projection) {
|
||||
int index = contactsCursor.getColumnIndex(column);
|
||||
// Since we might be getting various kinds of data, Object is the best we can do
|
||||
Object data;
|
||||
int type;
|
||||
String data;
|
||||
if (index == -1) {
|
||||
// This contact didn't have the requested column? Something is very wrong.
|
||||
// If you are experiencing this, please open a bug report indicating how you got here
|
||||
Log.e("ContactsHelper", "Got a contact which does not have a requested column");
|
||||
Log.e(LOG_TAG, "Got a contact which does not have a requested column");
|
||||
continue;
|
||||
}
|
||||
|
||||
type = contactsCursor.getType(index);
|
||||
switch (type) {
|
||||
case Cursor.FIELD_TYPE_INTEGER:
|
||||
data = contactsCursor.getInt(index);
|
||||
break;
|
||||
case Cursor.FIELD_TYPE_FLOAT:
|
||||
data = contactsCursor.getFloat(index);
|
||||
break;
|
||||
case Cursor.FIELD_TYPE_STRING:
|
||||
data = contactsCursor.getString(index);
|
||||
break;
|
||||
case Cursor.FIELD_TYPE_BLOB:
|
||||
data = contactsCursor.getBlob(index);
|
||||
break;
|
||||
default:
|
||||
Log.e("ContactsHelper", "Got an undefined type of column " + column);
|
||||
continue;
|
||||
}
|
||||
data = contactsCursor.getString(index);
|
||||
|
||||
requestedData.put(column, data);
|
||||
}
|
||||
|
||||
toReturn.put(new uID(lookupKey), requestedData);
|
||||
toReturn.put(uID, requestedData);
|
||||
} while (contactsCursor.moveToNext());
|
||||
}
|
||||
}
|
||||
@ -391,4 +405,17 @@ public class ContactsHelper {
|
||||
return contactLookupKey.equals(other);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception to indicate that a specified contact was not found
|
||||
*/
|
||||
public static class ContactNotFoundException extends Exception {
|
||||
public ContactNotFoundException(uID contactID) {
|
||||
super("Unable to find contact with ID " + contactID);
|
||||
}
|
||||
|
||||
public ContactNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +108,13 @@ public class NetworkPacket {
|
||||
return mBody.optInt(key, defaultValue);
|
||||
}
|
||||
|
||||
public void set(String key, int value) {
|
||||
try {
|
||||
mBody.put(key, value);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public long getLong(String key) {
|
||||
return mBody.optLong(key, -1);
|
||||
}
|
||||
@ -116,7 +123,7 @@ public class NetworkPacket {
|
||||
return mBody.optLong(key, defaultValue);
|
||||
}
|
||||
|
||||
public void set(String key, int value) {
|
||||
public void set(String key, long value) {
|
||||
try {
|
||||
mBody.put(key, value);
|
||||
} catch (Exception ignored) {
|
||||
|
@ -26,12 +26,12 @@ package org.kde.kdeconnect.Plugins.ContactsPlugin;
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.provider.ContactsContract;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.ContactsHelper;
|
||||
import org.kde.kdeconnect.Helpers.ContactsHelper.VCardBuilder;
|
||||
import org.kde.kdeconnect.Helpers.ContactsHelper.uID;
|
||||
import org.kde.kdeconnect.Helpers.ContactsHelper.ContactNotFoundException;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
@ -141,9 +141,10 @@ public class ContactsPlugin extends Plugin {
|
||||
*
|
||||
* @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) {
|
||||
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(),
|
||||
@ -151,16 +152,9 @@ public class ContactsPlugin extends Plugin {
|
||||
|
||||
// Build the timestamp line
|
||||
// Maybe one day this should be changed into the vcard-standard REV key
|
||||
List<uID> uIDs = new ArrayList<>();
|
||||
uIDs.add(uID);
|
||||
|
||||
final String[] contactsProjection = new String[]{
|
||||
ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP
|
||||
};
|
||||
|
||||
Map<uID, Map<String, Object>> timestamp = ContactsHelper.getColumnsFromContactsForIDs(context, uIDs, contactsProjection);
|
||||
Long timestamp = ContactsHelper.getContactTimestamp(context, uID);
|
||||
vcard.appendLine("X-KDECONNECT-TIMESTAMP",
|
||||
timestamp.get(uID).get(ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP).toString());
|
||||
timestamp.toString());
|
||||
|
||||
return vcard;
|
||||
}
|
||||
@ -178,26 +172,19 @@ public class ContactsPlugin extends Plugin {
|
||||
private boolean handleRequestAllUIDsTimestamps(@SuppressWarnings("unused") NetworkPacket np) {
|
||||
NetworkPacket reply = new NetworkPacket(PACKET_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS);
|
||||
|
||||
List<uID> uIDs = ContactsHelper.getAllContactContactIDs(context);
|
||||
Map<uID, Long> uIDsToTimestamps = ContactsHelper.getAllContactTimestamps(context);
|
||||
|
||||
List<String> uIDsAsStrings = new ArrayList<>(uIDs.size());
|
||||
int contactCount = uIDsToTimestamps.size();
|
||||
|
||||
for (uID uID : uIDs) {
|
||||
uIDsAsStrings.add(uID.toString());
|
||||
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());
|
||||
}
|
||||
|
||||
final String[] contactsProjection = new String[]{
|
||||
ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP
|
||||
};
|
||||
|
||||
reply.set("uids", uIDsAsStrings);
|
||||
|
||||
// Add last-modified timestamps
|
||||
Map<uID, Map<String, Object>> uIDsToTimestamps = ContactsHelper.getColumnsFromContactsForIDs(context, uIDs, contactsProjection);
|
||||
for (uID ID : uIDsToTimestamps.keySet()) {
|
||||
Map<String, Object> data = uIDsToTimestamps.get(ID);
|
||||
reply.set(ID.toString(), (Integer) data.get(ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP));
|
||||
}
|
||||
reply.set("uids", uIDs);
|
||||
|
||||
device.sendPacket(reply);
|
||||
|
||||
@ -230,12 +217,17 @@ public class ContactsPlugin extends Plugin {
|
||||
for (uID uID : uIDsToVCards.keySet()) {
|
||||
VCardBuilder vcard = uIDsToVCards.get(uID);
|
||||
|
||||
vcard = this.addVCardMetadata(vcard, 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());
|
||||
// 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user