diff options
Diffstat (limited to 'OpenKeychain')
3 files changed, 248 insertions, 27 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 @@      <uses-permission android:name="android.permission.READ_CONTACTS" />      <uses-permission android:name="android.permission.WRITE_CONTACTS" />      <uses-permission android:name="android.permission.READ_PROFILE" /> +    <uses-permission android:name="android.permission.WRITE_PROFILE" />      <!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! -->      <application 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..18f1ef8a7 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<Cursor> { @@ -119,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       */ @@ -126,19 +129,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) {//all secret keys are linked to "me" profile in contacts +            contactId = ContactHelper.getMainProfileContactId(resolver); +            List<String> 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 +258,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 b3eb36157..c07d7a36b 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<String> getMainProfileContactName(Context context) { +    public static List<String> getMainProfileContactName(Context context) {          ContentResolver resolver = context.getContentResolver();          Cursor profileCursor = resolver.query(                  ContactsContract.Profile.CONTENT_URI, @@ -214,6 +214,53 @@ 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, +                new String[]{ +                        ContactsContract.Profile._ID +                }, +                null, null, null); +        if (profileCursor == null) { +            return -1; +        } + +        profileCursor.moveToNext(); +        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); + +            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<String> getContactMails(Context context) {          ContentResolver resolver = context.getContentResolver();          Cursor mailCursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, @@ -269,7 +316,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 @@ -363,7 +410,8 @@ public class ContactHelper {              KeychainContract.KeyRings.IS_EXPIRED,              KeychainContract.KeyRings.IS_REVOKED,              KeychainContract.KeyRings.VERIFIED, -            KeychainContract.KeyRings.HAS_SECRET}; +            KeychainContract.KeyRings.HAS_SECRET, +            KeychainContract.KeyRings.HAS_ANY_SECRET};      public static final int INDEX_MASTER_KEY_ID = 0;      public static final int INDEX_USER_ID = 1; @@ -371,6 +419,7 @@ public class ContactHelper {      public static final int INDEX_IS_REVOKED = 3;      public static final int INDEX_VERIFIED = 4;      public static final int INDEX_HAS_SECRET = 5; +    public static final int INDEX_HAS_ANY_SECRET = 6;      /**       * Write/Update the current OpenKeychain keys to the contact db @@ -379,6 +428,8 @@ public class ContactHelper {          ContentResolver resolver = context.getContentResolver();          Set<Long> deletedKeys = getRawContactMasterKeyIds(resolver); +        writeKeysToMainProfileContact(context); +          if (Constants.DEBUG_SYNC_REMOVE_CONTACTS) {              debugDeleteRawContacts(resolver);          } @@ -395,9 +446,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 +461,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,9 +472,11 @@ public class ContactHelper {                  ArrayList<ContentProviderOperation> 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) { -                    Log.d(Constants.TAG, "Expired or revoked or unverified: Deleting rawContactId " + rawContactId); +                // 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);                      } @@ -456,43 +512,166 @@ public class ContactHelper {      }      /** -     * Delete all raw contacts associated to OpenKeychain. +     * 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<Long> 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 isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; +                boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; + +                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); + +                        ArrayList<ContentProviderOperation> 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 +     * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html +     * +     * @param ops +     * @param masterKeyId +     */ +    private static void insertMainProfileRawContact(ArrayList<ContentProviderOperation> 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 main profile table ("me" contact) +     * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html +     * +     * @param resolver +     * @param masterKeyId +     * @return +     */ +    private static int deleteMainProfileRawContactByMasterKeyId(ContentResolver resolver, +                                                                long masterKeyId) { +        // 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(); + +        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, 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();          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)                  });      } +    /** +     * 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();          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)                  }); @@ -521,6 +700,28 @@ public class ContactHelper {      }      /** +     * @return a set of all key master key ids currently present in the contact db +     */ +    private static Set<Long> getMainProfileMasterKeyIds(ContentResolver resolver) { +        HashSet<Long> 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       *       * @return raw contact id or -1 if not found | 
