From d4375b7dfc23213caca1b4eb2e86c54338e86605 Mon Sep 17 00:00:00 2001 From: Adithya Abraham Philip Date: Fri, 13 Mar 2015 03:13:22 +0530 Subject: [PATCH 1/3] links own keys to me profile in contacts --- OpenKeychain/src/main/AndroidManifest.xml | 1 + .../keychain/util/ContactHelper.java | 127 ++++++++++++++++-- 2 files changed, 119 insertions(+), 9 deletions(-) diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 5d3a106de..0e7baf039 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -65,6 +65,7 @@ + deletedKeys = getRawContactMasterKeyIds(resolver); + writeKeysToMainProfileContact(context); + if (Constants.DEBUG_SYNC_REMOVE_CONTACTS) { debugDeleteRawContacts(resolver); } @@ -395,9 +399,13 @@ public class ContactHelper { // e.printStackTrace(); // } - // Load all Keys from OK - Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), KEYS_TO_CONTACT_PROJECTION, - null, null, null); + // Load all public Keys from OK + // TODO: figure out why using selectionArgs does not work in this case + Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), + KEYS_TO_CONTACT_PROJECTION, + KeychainContract.KeyRings.HAS_ANY_SECRET + "=0", + null, null); + if (cursor != null) { while (cursor.moveToNext()) { long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); @@ -406,7 +414,6 @@ public class ContactHelper { boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0; - boolean isSecret = cursor.getInt(INDEX_HAS_SECRET) != 0; Log.d(Constants.TAG, "masterKeyId: " + masterKeyId); @@ -418,8 +425,9 @@ public class ContactHelper { ArrayList ops = new ArrayList<>(); - // Do not store expired or revoked keys in contact db - and remove them if they already exist - if (isExpired || isRevoked || !isVerified&&!isSecret) { + // Do not store expired or revoked or unverified keys in contact db - and + // remove them if they already exist. Secret keys do not reach this point + if (isExpired || isRevoked || !isVerified) { Log.d(Constants.TAG, "Expired or revoked or unverified: Deleting rawContactId " + rawContactId); if (rawContactId != -1) { deleteRawContactById(resolver, rawContactId); @@ -455,6 +463,83 @@ public class ContactHelper { } } + public static void writeKeysToMainProfileContact(Context context) { + ContentResolver resolver = context.getContentResolver(); + Set keysToDelete = getMainProfileMasterKeyIds(resolver); + + // get all keys which have associated secret keys + // TODO: figure out why using selectionArgs does not work in this case + Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), + KEYS_TO_CONTACT_PROJECTION, + KeychainContract.KeyRings.HAS_ANY_SECRET + "!=0", + null, null); + if (cursor != null) { + while (cursor.moveToNext()) { + long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); + + boolean existsInMainProfile = keysToDelete.remove(masterKeyId); + if (!existsInMainProfile) { + long rawContactId = -1;//new raw contact + + String keyIdShort = KeyFormattingUtils.convertKeyIdToHexShort(masterKeyId); + Log.d(Constants.TAG, "masterKeyId with secret " + masterKeyId); + + ArrayList ops = new ArrayList<>(); + insertMainProfileRawContact(ops, masterKeyId); + writeContactKey(ops, context, rawContactId, masterKeyId, keyIdShort); + + try { + resolver.applyBatch(ContactsContract.AUTHORITY, ops); + } catch (Exception e) { + Log.w(Constants.TAG, e); + } + } + } + } + + for (long masterKeyId : keysToDelete) { + deleteMainProfileRawContactByMasterKeyId(resolver, masterKeyId); + Log.d(Constants.TAG, "Delete main profile raw contact with masterKeyId " + masterKeyId); + } + } + + /** + * Inserts a raw contact into the table defined by ContactsContract.Profile + * + * @param ops + * @param masterKeyId + */ + private static void insertMainProfileRawContact(ArrayList ops, + long masterKeyId) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE) + .withValue(ContactsContract.RawContacts.SOURCE_ID, Long.toString(masterKeyId)) + .build()); + } + + /** + * deletes a raw contact from the profile table + * + * @param resolver + * @param masterKeyId + * @return + */ + private static int deleteMainProfileRawContactByMasterKeyId(ContentResolver resolver, + long masterKeyId) { + //allows us to actually wipe the RawContact from the device, otherwise would be just flagged + //for deletion + Uri deleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon(). + appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); + + return resolver.delete(deleteUri, + ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + + ContactsContract.RawContacts.SOURCE_ID + "=?", + new String[]{ + Constants.ACCOUNT_TYPE, Long.toString(masterKeyId) + }); + } + /** * Delete all raw contacts associated to OpenKeychain. */ @@ -479,7 +564,8 @@ public class ContactHelper { appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); return resolver.delete(deleteUri, - ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts._ID + "=?", + ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + + ContactsContract.RawContacts._ID + "=?", new String[]{ Constants.ACCOUNT_TYPE, Long.toString(rawContactId) }); @@ -492,7 +578,8 @@ public class ContactHelper { appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); return resolver.delete(deleteUri, - ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=?", + ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + + ContactsContract.RawContacts.SOURCE_ID + "=?", new String[]{ Constants.ACCOUNT_TYPE, Long.toString(masterKeyId) }); @@ -520,6 +607,28 @@ public class ContactHelper { return result; } + /** + * @return a set of all key master key ids currently present in the contact db + */ + private static Set getMainProfileMasterKeyIds(ContentResolver resolver) { + HashSet result = new HashSet<>(); + Cursor masterKeyIds = resolver.query(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI, + new String[]{ + ContactsContract.RawContacts.SOURCE_ID + }, + ContactsContract.RawContacts.ACCOUNT_TYPE + "=?", + new String[]{ + Constants.ACCOUNT_TYPE + }, null); + if (masterKeyIds != null) { + while (masterKeyIds.moveToNext()) { + result.add(masterKeyIds.getLong(0)); + } + masterKeyIds.close(); + } + return result; + } + /** * This will search the contact db for a raw contact with a given master key id * From 759009ddb4ac11f4065ecfa14e17ed330bd01989 Mon Sep 17 00:00:00 2001 From: Adithya Abraham Philip Date: Fri, 13 Mar 2015 04:04:11 +0530 Subject: [PATCH 2/3] added own profile support in linekd system contact --- .../keychain/ui/ViewKeyFragment.java | 32 +++++++++++---- .../keychain/util/ContactHelper.java | 39 +++++++++++++++++-- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java index eae283269..68da3b58c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -44,6 +44,8 @@ import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; +import java.util.List; + public class ViewKeyFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks { @@ -126,19 +128,35 @@ public class ViewKeyFragment extends LoaderFragment implements final Context context = mSystemContactName.getContext(); final ContentResolver resolver = context.getContentResolver(); - final long contactId = ContactHelper.findContactId(resolver, masterKeyId); - final String contactName = ContactHelper.getContactName(resolver, contactId); + long contactId; + String contactName = null; + + if(mIsSecret) { + contactId = ContactHelper.getMainProfileContactId(resolver); + List mainProfileNames = ContactHelper.getMainProfileContactName(context); + if(mainProfileNames!=null) contactName = mainProfileNames.get(0); + + } else { + contactId = ContactHelper.findContactId(resolver, masterKeyId); + contactName = ContactHelper.getContactName(resolver, contactId); + } if (contactName != null) {//contact name exists for given master key mSystemContactName.setText(contactName); - Bitmap picture = ContactHelper.loadPhotoByMasterKeyId(resolver, masterKeyId, true); + Bitmap picture; + if(mIsSecret) { + picture = ContactHelper.loadMainProfilePhoto(resolver, false); + } else { + picture = ContactHelper.loadPhotoByMasterKeyId(resolver,masterKeyId,false); + } if (picture != null) mSystemContactPicture.setImageBitmap(picture); + final long finalContactId = contactId; mSystemContactLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - launchContactActivity(contactId, context); + launchContactActivity(finalContactId, context); } }); mSystemContactLoaded = true; @@ -239,14 +257,14 @@ public class ViewKeyFragment extends LoaderFragment implements switch (loader.getId()) { case LOADER_ID_UNIFIED: { if (data.moveToFirst()) { + + mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + //TODO system to allow immediate refreshing of system contact on verification if (!mSystemContactLoaded) {//ensure we load linked system contact only once long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); loadLinkedSystemContact(masterKeyId); } - - mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; - // load user ids after we know if it's a secret key mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null); mUserIds.setAdapter(mUserIdsAdapter); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index 1b52e4e6b..cb6c39188 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -190,7 +190,7 @@ public class ContactHelper { * @param context * @return */ - private static List getMainProfileContactName(Context context) { + public static List getMainProfileContactName(Context context) { ContentResolver resolver = context.getContentResolver(); Cursor profileCursor = resolver.query( ContactsContract.Profile.CONTENT_URI, @@ -214,6 +214,38 @@ public class ContactHelper { return new ArrayList<>(names); } + public static long getMainProfileContactId(ContentResolver resolver) { + Cursor profileCursor = resolver.query( + ContactsContract.Profile.CONTENT_URI, + new String[]{ + ContactsContract.Profile._ID + }, + null, null, null); + if (profileCursor == null) { + return -1; + } + + profileCursor.moveToNext(); + return profileCursor.getLong(0); + } + + public static Bitmap loadMainProfilePhoto(ContentResolver contentResolver, boolean highRes) { + try { + long mainProfileContactId = getMainProfileContactId(contentResolver); + + Uri contactUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, + Long.toString(mainProfileContactId)); + InputStream photoInputStream = + ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, contactUri, highRes); + if (photoInputStream == null) { + return null; + } + return BitmapFactory.decodeStream(photoInputStream); + } catch (Throwable ignored) { + return null; + } + } + public static List getContactMails(Context context) { ContentResolver resolver = context.getContentResolver(); Cursor mailCursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, @@ -269,7 +301,7 @@ public class ContactHelper { /** * returns the CONTACT_ID of the raw contact to which a masterKeyId is associated, if the - * raw contact has not been marked for deletion + * raw contact has not been marked for deletion. * * @param resolver * @param masterKeyId @@ -428,7 +460,8 @@ public class ContactHelper { // Do not store expired or revoked or unverified keys in contact db - and // remove them if they already exist. Secret keys do not reach this point if (isExpired || isRevoked || !isVerified) { - Log.d(Constants.TAG, "Expired or revoked or unverified: Deleting rawContactId " + rawContactId); + Log.d(Constants.TAG, "Expired or revoked or unverified: Deleting rawContactId " + + rawContactId); if (rawContactId != -1) { deleteRawContactById(resolver, rawContactId); } From d7ef2c1b9ed9a98c33049c9dc8b43611cf5b99ce Mon Sep 17 00:00:00 2001 From: Adithya Abraham Philip Date: Fri, 13 Mar 2015 04:49:34 +0530 Subject: [PATCH 3/3] added some documentation --- .../keychain/ui/ViewKeyFragment.java | 9 +- .../keychain/util/ContactHelper.java | 105 ++++++++++++++---- 2 files changed, 87 insertions(+), 27 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java index 68da3b58c..18f1ef8a7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -121,6 +121,7 @@ public class ViewKeyFragment extends LoaderFragment implements /** * Checks if a system contact exists for given masterKeyId, and if it does, sets name, picture * and onClickListener for the linked system contact's layout + * In the case of a secret key, "me" contact details are loaded * * @param masterKeyId */ @@ -131,10 +132,10 @@ public class ViewKeyFragment extends LoaderFragment implements long contactId; String contactName = null; - if(mIsSecret) { + if (mIsSecret) {//all secret keys are linked to "me" profile in contacts contactId = ContactHelper.getMainProfileContactId(resolver); List mainProfileNames = ContactHelper.getMainProfileContactName(context); - if(mainProfileNames!=null) contactName = mainProfileNames.get(0); + if (mainProfileNames != null) contactName = mainProfileNames.get(0); } else { contactId = ContactHelper.findContactId(resolver, masterKeyId); @@ -145,10 +146,10 @@ public class ViewKeyFragment extends LoaderFragment implements mSystemContactName.setText(contactName); Bitmap picture; - if(mIsSecret) { + if (mIsSecret) { picture = ContactHelper.loadMainProfilePhoto(resolver, false); } else { - picture = ContactHelper.loadPhotoByMasterKeyId(resolver,masterKeyId,false); + picture = ContactHelper.loadPhotoByMasterKeyId(resolver, masterKeyId, false); } if (picture != null) mSystemContactPicture.setImageBitmap(picture); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index cb6c39188..c07d7a36b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -214,6 +214,13 @@ public class ContactHelper { return new ArrayList<>(names); } + /** + * returns the CONTACT_ID of the main ("me") contact + * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html + * + * @param resolver + * @return + */ public static long getMainProfileContactId(ContentResolver resolver) { Cursor profileCursor = resolver.query( ContactsContract.Profile.CONTENT_URI, @@ -229,6 +236,14 @@ public class ContactHelper { return profileCursor.getLong(0); } + /** + * loads the profile picture of the main ("me") contact + * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html + * + * @param contentResolver + * @param highRes true for large image if present, false for thumbnail + * @return bitmap of loaded photo + */ public static Bitmap loadMainProfilePhoto(ContentResolver contentResolver, boolean highRes) { try { long mainProfileContactId = getMainProfileContactId(contentResolver); @@ -496,6 +511,12 @@ public class ContactHelper { } } + /** + * Links all keys with secrets to the main ("me") contact + * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html + * + * @param context + */ public static void writeKeysToMainProfileContact(Context context) { ContentResolver resolver = context.getContentResolver(); Set keysToDelete = getMainProfileMasterKeyIds(resolver); @@ -509,22 +530,28 @@ public class ContactHelper { if (cursor != null) { while (cursor.moveToNext()) { long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); + boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; + boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; - boolean existsInMainProfile = keysToDelete.remove(masterKeyId); - if (!existsInMainProfile) { - long rawContactId = -1;//new raw contact + if (!isExpired && !isRevoked) { + // if expired or revoked will not be removed from keysToDelete or inserted + // into main profile ("me" contact) + boolean existsInMainProfile = keysToDelete.remove(masterKeyId); + if (!existsInMainProfile) { + long rawContactId = -1;//new raw contact - String keyIdShort = KeyFormattingUtils.convertKeyIdToHexShort(masterKeyId); - Log.d(Constants.TAG, "masterKeyId with secret " + masterKeyId); + String keyIdShort = KeyFormattingUtils.convertKeyIdToHexShort(masterKeyId); + Log.d(Constants.TAG, "masterKeyId with secret " + masterKeyId); - ArrayList ops = new ArrayList<>(); - insertMainProfileRawContact(ops, masterKeyId); - writeContactKey(ops, context, rawContactId, masterKeyId, keyIdShort); + ArrayList ops = new ArrayList<>(); + insertMainProfileRawContact(ops, masterKeyId); + writeContactKey(ops, context, rawContactId, masterKeyId, keyIdShort); - try { - resolver.applyBatch(ContactsContract.AUTHORITY, ops); - } catch (Exception e) { - Log.w(Constants.TAG, e); + try { + resolver.applyBatch(ContactsContract.AUTHORITY, ops); + } catch (Exception e) { + Log.w(Constants.TAG, e); + } } } } @@ -538,6 +565,7 @@ public class ContactHelper { /** * Inserts a raw contact into the table defined by ContactsContract.Profile + * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html * * @param ops * @param masterKeyId @@ -552,7 +580,8 @@ public class ContactHelper { } /** - * deletes a raw contact from the profile table + * deletes a raw contact from the main profile table ("me" contact) + * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html * * @param resolver * @param masterKeyId @@ -560,8 +589,8 @@ public class ContactHelper { */ private static int deleteMainProfileRawContactByMasterKeyId(ContentResolver resolver, long masterKeyId) { - //allows us to actually wipe the RawContact from the device, otherwise would be just flagged - //for deletion + // CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise + // would be just flagged for deletion Uri deleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon(). appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); @@ -574,25 +603,47 @@ public class ContactHelper { } /** - * Delete all raw contacts associated to OpenKeychain. + * Delete all raw contacts associated to OpenKeychain, including those from "me" contact + * defined by ContactsContract.Profile + * + * @return number of rows deleted */ private static int debugDeleteRawContacts(ContentResolver resolver) { - //allows us to actually wipe the RawContact from the device, otherwise would be just flagged - //for deletion + // CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise + // would be just flagged for deletion Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon(). appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); Log.d(Constants.TAG, "Deleting all raw contacts associated to OK..."); - return resolver.delete(deleteUri, + int delete = resolver.delete(deleteUri, ContactsContract.RawContacts.ACCOUNT_TYPE + "=?", new String[]{ Constants.ACCOUNT_TYPE }); + + Uri mainProfileDeleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon() + .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); + + delete += resolver.delete(mainProfileDeleteUri, + ContactsContract.RawContacts.ACCOUNT_TYPE + "=?", + new String[]{ + Constants.ACCOUNT_TYPE + }); + + return delete; } + /** + * Deletes raw contacts from ContactsContract.RawContacts based on rawContactId. Does not + * delete contacts from the "me" contact defined in ContactsContract.Profile + * + * @param resolver + * @param rawContactId + * @return number of rows deleted + */ private static int deleteRawContactById(ContentResolver resolver, long rawContactId) { - //allows us to actually wipe the RawContact from the device, otherwise would be just flagged - //for deletion + // CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise + // would be just flagged for deletion Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon(). appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); @@ -604,9 +655,17 @@ public class ContactHelper { }); } + /** + * Deletes raw contacts from ContactsContract.RawContacts based on masterKeyId. Does not + * delete contacts from the "me" contact defined in ContactsContract.Profile + * + * @param resolver + * @param masterKeyId + * @return number of rows deleted + */ private static int deleteRawContactByMasterKeyId(ContentResolver resolver, long masterKeyId) { - //allows us to actually wipe the RawContact from the device, otherwise would be just flagged - //for deletion + // CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise + // would be just flagged for deletion Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon(). appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();