コード例 #1
0
  private Intent createSendIntent(byte[] resultBytes) {
    Intent sendIntent;
    sendIntent = new Intent(Intent.ACTION_SEND);
    sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME);
    sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes));

    EncryptActivity modeInterface = (EncryptActivity) getActivity();
    EncryptModeFragment modeFragment = modeInterface.getModeFragment();
    if (!modeFragment.isAsymmetric()) {
      return sendIntent;
    }

    String[] encryptionUserIds = modeFragment.getAsymmetricEncryptionUserIds();
    if (encryptionUserIds == null) {
      return sendIntent;
    }

    Set<String> users = new HashSet<>();
    for (String user : encryptionUserIds) {
      KeyRing.UserId userId = KeyRing.splitUserId(user);
      if (userId.email != null) {
        users.add(userId.email);
      }
    }
    // pass trough email addresses as extra for email applications
    sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));

    return sendIntent;
  }
コード例 #2
0
  @Override
  public void bindView(View view, Context context, Cursor cursor) {
    Highlighter highlighter = new Highlighter(context, mQuery);
    ViewHolderItem h = (ViewHolderItem) view.getTag();

    String userId = cursor.getString(mIndexUserId);
    KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);

    if (userIdSplit.name != null) {
      h.mainUserId.setText(highlighter.highlight(userIdSplit.name));
    } else {
      h.mainUserId.setText(R.string.user_id_no_name);
    }
    if (userIdSplit.email != null) {
      h.mainUserIdRest.setVisibility(View.VISIBLE);
      h.mainUserIdRest.setText(highlighter.highlight(userIdSplit.email));
    } else {
      h.mainUserIdRest.setVisibility(View.GONE);
    }

    boolean duplicate = cursor.getLong(mIndexDuplicateUserId) > 0;
    if (duplicate) {
      String dateTime =
          DateUtils.formatDateTime(
              context,
              cursor.getLong(mIndexCreation) * 1000,
              DateUtils.FORMAT_SHOW_DATE
                  | DateUtils.FORMAT_SHOW_TIME
                  | DateUtils.FORMAT_SHOW_YEAR
                  | DateUtils.FORMAT_ABBREV_MONTH);
      h.creation.setText(context.getString(R.string.label_key_created, dateTime));
      h.creation.setVisibility(View.VISIBLE);
    } else {
      h.creation.setVisibility(View.GONE);
    }

    boolean enabled;
    if (cursor.getInt(mIndexIsRevoked) != 0) {
      h.statusIcon.setVisibility(View.VISIBLE);
      KeyFormattingUtils.setStatusImage(
          mContext, h.statusIcon, null, State.REVOKED, R.color.key_flag_gray);
      enabled = false;
    } else if (cursor.getInt(mIndexIsExpiry) != 0) {
      h.statusIcon.setVisibility(View.VISIBLE);
      KeyFormattingUtils.setStatusImage(
          mContext, h.statusIcon, null, State.EXPIRED, R.color.key_flag_gray);
      enabled = false;
    } else {
      h.statusIcon.setVisibility(View.GONE);
      enabled = true;
    }

    h.statusIcon.setTag(enabled);
  }
コード例 #3
0
ファイル: ContactHelper.java プロジェクト: BenEdridge/apg
  /**
   * 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) {
    // deletes contacts hidden by the user so they can be reinserted if necessary
    deleteFlaggedMainProfileRawContacts(resolver);

    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)
      try {
        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;
          KeyRing.UserId userIdSplit = KeyRing.splitUserId(cursor.getString(INDEX_USER_ID));

          if (!isExpired && !isRevoked && userIdSplit.name != null) {
            // 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

              Log.d(Constants.TAG, "masterKeyId with secret " + masterKeyId);

              ArrayList<ContentProviderOperation> ops = new ArrayList<>();
              insertMainProfileRawContact(ops, masterKeyId);
              writeContactKey(ops, context, rawContactId, masterKeyId, userIdSplit.name);

              try {
                resolver.applyBatch(ContactsContract.AUTHORITY, ops);
              } catch (Exception e) {
                Log.w(Constants.TAG, e);
              }
            }
          }
        }
      } finally {
        cursor.close();
      }

    for (long masterKeyId : keysToDelete) {
      deleteMainProfileRawContactByMasterKeyId(resolver, masterKeyId);
      Log.d(Constants.TAG, "Delete main profile raw contact with masterKeyId " + masterKeyId);
    }
  }
コード例 #4
0
ファイル: ContactHelper.java プロジェクト: BenEdridge/apg
 /** Write all known email addresses of a key (derived from user ids) to a given raw contact */
 private static void writeContactEmail(
     ArrayList<ContentProviderOperation> ops,
     ContentResolver resolver,
     long rawContactId,
     long masterKeyId) {
   ops.add(
       selectByRawContactAndItemType(
               ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI),
               rawContactId,
               ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
           .build());
   Cursor ids =
       resolver.query(
           UserPackets.buildUserIdsUri(masterKeyId),
           new String[] {UserPackets.USER_ID},
           UserPackets.IS_REVOKED + "=0",
           null,
           null);
   if (ids != null) {
     while (ids.moveToNext()) {
       KeyRing.UserId userId = KeyRing.splitUserId(ids.getString(0));
       if (userId.email != null) {
         ops.add(
             referenceRawContact(
                     ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI),
                     rawContactId)
                 .withValue(
                     ContactsContract.Data.MIMETYPE,
                     ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                 .withValue(ContactsContract.CommonDataKinds.Email.DATA, userId.email)
                 .build());
       }
     }
     ids.close();
   }
 }
コード例 #5
0
  @Override
  public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

    MatrixCursor matrix =
        new MatrixCursor(new String[] {"_id", "user_data", "grouped"}) {
          @Override
          public byte[] getBlob(int column) {
            return super.getBlob(column);
          }
        };
    data.moveToFirst();

    long lastMasterKeyId = 0;
    String lastName = "";
    ArrayList<String> uids = new ArrayList<>();

    boolean header = true;

    // Iterate over all rows
    while (!data.isAfterLast()) {
      long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
      String userId = data.getString(INDEX_USER_ID);
      KeyRing.UserId pieces = KeyRing.splitUserId(userId);

      // Two cases:

      boolean grouped = masterKeyId == lastMasterKeyId;
      boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name);
      // Remember for next loop
      lastName = pieces.name;

      Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped"));

      if (!subGrouped) {
        // 1. This name should NOT be grouped with the previous, so we flush the buffer

        Parcel p = Parcel.obtain();
        p.writeStringList(uids);
        byte[] d = p.marshall();
        p.recycle();

        matrix.addRow(new Object[] {lastMasterKeyId, d, header ? 1 : 0});
        // indicate that we have a header for this masterKeyId
        header = false;

        // Now clear the buffer, and add the new user id, for the next round
        uids.clear();
      }

      // 2. This name should be grouped with the previous, just add to buffer
      uids.add(userId);
      lastMasterKeyId = masterKeyId;

      // If this one wasn't grouped, the next one's gotta be a header
      if (!grouped) {
        header = true;
      }

      // Regardless of the outcome, move to next entry
      data.moveToNext();
    }

    // If there is anything left in the buffer, flush it one last time
    if (!uids.isEmpty()) {

      Parcel p = Parcel.obtain();
      p.writeStringList(uids);
      byte[] d = p.marshall();
      p.recycle();

      matrix.addRow(new Object[] {lastMasterKeyId, d, header ? 1 : 0});
    }

    mUserIdsAdapter.swapCursor(matrix);
  }
コード例 #6
0
 public OpenPgpUtils.UserId getUserId() {
   return KeyRing.splitUserId(getRawUserId());
 }
コード例 #7
0
  @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated
  public static void setStatus(Context context, StatusHolder holder, DecryptVerifyResult result) {

    OpenPgpSignatureResult signatureResult = result.getSignatureResult();

    if (holder.hasEncrypt()) {
      int encText, encIcon, encColor;
      if (signatureResult != null && signatureResult.isSignatureOnly()) {
        encIcon = R.drawable.status_lock_open_24dp;
        encText = R.string.decrypt_result_not_encrypted;
        encColor = R.color.android_red_light;
      } else {
        encIcon = R.drawable.status_lock_closed_24dp;
        encText = R.string.decrypt_result_encrypted;
        encColor = R.color.android_green_light;
      }

      int encColorRes = context.getResources().getColor(encColor);
      holder.getEncryptionStatusIcon().setColorFilter(encColorRes, PorterDuff.Mode.SRC_IN);
      holder
          .getEncryptionStatusIcon()
          .setImageDrawable(context.getResources().getDrawable(encIcon));
      holder.getEncryptionStatusText().setText(encText);
      holder.getEncryptionStatusText().setTextColor(encColorRes);
    }

    int sigText, sigIcon, sigColor;
    int sigActionText, sigActionIcon;

    if (signatureResult == null) {

      sigText = R.string.decrypt_result_no_signature;
      sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
      sigColor = R.color.bg_gray;

      // won't be used, but makes compiler happy
      sigActionText = 0;
      sigActionIcon = 0;

    } else
      switch (signatureResult.getStatus()) {
        case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED:
          {
            sigText = R.string.decrypt_result_signature_certified;
            sigIcon = R.drawable.status_signature_verified_cutout_24dp;
            sigColor = R.color.android_green_light;

            sigActionText = R.string.decrypt_result_action_show;
            sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
            break;
          }

        case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED:
          {
            sigText = R.string.decrypt_result_signature_uncertified;
            sigIcon = R.drawable.status_signature_unverified_cutout_24dp;
            sigColor = R.color.android_orange_light;

            sigActionText = R.string.decrypt_result_action_show;
            sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
            break;
          }

        case OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED:
          {
            sigText = R.string.decrypt_result_signature_revoked_key;
            sigIcon = R.drawable.status_signature_revoked_cutout_24dp;
            sigColor = R.color.android_red_light;

            sigActionText = R.string.decrypt_result_action_show;
            sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
            break;
          }

        case OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED:
          {
            sigText = R.string.decrypt_result_signature_expired_key;
            sigIcon = R.drawable.status_signature_expired_cutout_24dp;
            sigColor = R.color.android_red_light;

            sigActionText = R.string.decrypt_result_action_show;
            sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
            break;
          }

        case OpenPgpSignatureResult.SIGNATURE_KEY_MISSING:
          {
            sigText = R.string.decrypt_result_signature_missing_key;
            sigIcon = R.drawable.status_signature_unknown_cutout_24dp;
            sigColor = R.color.android_red_light;

            sigActionText = R.string.decrypt_result_action_Lookup;
            sigActionIcon = R.drawable.ic_file_download_grey_24dp;
            break;
          }

        default:
        case OpenPgpSignatureResult.SIGNATURE_ERROR:
          {
            sigText = R.string.decrypt_result_invalid_signature;
            sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
            sigColor = R.color.android_red_light;

            sigActionText = R.string.decrypt_result_action_show;
            sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
            break;
          }
      }

    int sigColorRes = context.getResources().getColor(sigColor);
    holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN);
    holder.getSignatureStatusIcon().setImageDrawable(context.getResources().getDrawable(sigIcon));
    holder.getSignatureStatusText().setText(sigText);
    holder.getSignatureStatusText().setTextColor(sigColorRes);

    if (signatureResult != null) {

      holder.getSignatureLayout().setVisibility(View.VISIBLE);

      holder.getSignatureAction().setText(sigActionText);
      holder.getSignatureAction().setCompoundDrawablesWithIntrinsicBounds(0, 0, sigActionIcon, 0);

      String userId = signatureResult.getPrimaryUserId();
      KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
      if (userIdSplit.name != null) {
        holder.getSignatureUserName().setText(userIdSplit.name);
      } else {
        holder.getSignatureUserName().setText(R.string.user_id_no_name);
      }
      if (userIdSplit.email != null) {
        holder.getSignatureUserEmail().setVisibility(View.VISIBLE);
        holder.getSignatureUserEmail().setText(userIdSplit.email);
      } else {
        holder.getSignatureUserEmail().setVisibility(View.GONE);
      }

    } else {

      holder.getSignatureLayout().setVisibility(View.GONE);
    }
  }
コード例 #8
0
    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
      final Activity activity = getActivity();

      ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity);

      mRequiredInput = getArguments().getParcelable(EXTRA_REQUIRED_INPUT);

      CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme);

      // No title, see http://www.google.com/design/spec/components/dialogs.html#dialogs-alerts
      // alert.setTitle()

      if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) {
        LayoutInflater inflater = LayoutInflater.from(theme);
        View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null);
        alert.setView(view);

        mBackupCodeEditText = new EditText[4];
        mBackupCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1);
        mBackupCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2);
        mBackupCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3);
        mBackupCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4);
        setupEditTextFocusNext(mBackupCodeEditText);

        AlertDialog dialog = alert.create();
        dialog.setButton(
            DialogInterface.BUTTON_POSITIVE,
            activity.getString(R.string.btn_unlock),
            (DialogInterface.OnClickListener) null);
        return dialog;
      }

      long subKeyId = mRequiredInput.getSubKeyId();

      LayoutInflater inflater = LayoutInflater.from(theme);
      mLayout = (ViewAnimator) inflater.inflate(R.layout.passphrase_dialog, null);
      alert.setView(mLayout);

      mPassphraseText = (TextView) mLayout.findViewById(R.id.passphrase_text);
      mPassphraseEditText = (EditText) mLayout.findViewById(R.id.passphrase_passphrase);

      View vTimeToLiveLayout = mLayout.findViewById(R.id.remember_layout);
      vTimeToLiveLayout.setVisibility(mRequiredInput.mSkipCaching ? View.GONE : View.VISIBLE);

      mTimeToLiveSpinner = (CacheTTLSpinner) mLayout.findViewById(R.id.ttl_spinner);

      alert.setNegativeButton(
          android.R.string.cancel,
          new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int id) {
              dialog.cancel();
            }
          });

      String userId;
      CanonicalizedSecretKey.SecretKeyType keyType =
          CanonicalizedSecretKey.SecretKeyType.PASSPHRASE;

      String message;
      String hint;
      if (mRequiredInput.mType == RequiredInputType.PASSPHRASE_SYMMETRIC) {
        message = getString(R.string.passphrase_for_symmetric_encryption);
        hint = getString(R.string.label_passphrase);
      } else {
        try {
          ProviderHelper helper = new ProviderHelper(activity);
          mSecretRing =
              helper.getCanonicalizedSecretKeyRing(
                  KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId));
          // yes the inner try/catch block is necessary, otherwise the final variable
          // above can't be statically verified to have been set in all cases because
          // the catch clause doesn't return.
          try {
            String mainUserId = mSecretRing.getPrimaryUserIdWithFallback();
            KeyRing.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId);
            if (mainUserIdSplit.name != null) {
              userId = mainUserIdSplit.name;
            } else {
              userId = getString(R.string.user_id_no_name);
            }
          } catch (PgpKeyNotFoundException e) {
            userId = null;
          }

          keyType = mSecretRing.getSecretKey(subKeyId).getSecretKeyType();
          switch (keyType) {
            case PASSPHRASE:
              message = getString(R.string.passphrase_for, userId);
              hint = getString(R.string.label_passphrase);
              break;
            case PIN:
              message = getString(R.string.pin_for, userId);
              hint = getString(R.string.label_pin);
              break;
            case DIVERT_TO_CARD:
              message = getString(R.string.yubikey_pin_for, userId);
              hint = getString(R.string.label_pin);
              break;
              // special case: empty passphrase just returns the empty passphrase
            case PASSPHRASE_EMPTY:
              finishCaching(new Passphrase(""));
            default:
              throw new AssertionError("Unhandled SecretKeyType (should not happen)");
          }

        } catch (ProviderHelper.NotFoundException e) {
          alert.setTitle(R.string.title_key_not_found);
          alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId()));
          alert.setPositiveButton(
              android.R.string.ok,
              new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                  dismiss();
                }
              });
          alert.setCancelable(false);
          return alert.create();
        }
      }

      mPassphraseText.setText(message);
      mPassphraseEditText.setHint(hint);

      // Hack to open keyboard.
      // This is the only method that I found to work across all Android versions
      // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
      // Notes: * onCreateView can't be used because we want to add buttons to the dialog
      //        * opening in onActivityCreated does not work on Android 4.4
      mPassphraseEditText.setOnFocusChangeListener(
          new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
              mPassphraseEditText.post(
                  new Runnable() {
                    @Override
                    public void run() {
                      if (getActivity() == null || mPassphraseEditText == null) {
                        return;
                      }
                      InputMethodManager imm =
                          (InputMethodManager)
                              getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
                      imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
                    }
                  });
            }
          });
      mPassphraseEditText.requestFocus();

      mPassphraseEditText.setImeActionLabel(
          getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
      mPassphraseEditText.setOnEditorActionListener(this);

      if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD
              && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin())
          || keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
        mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
        mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
      } else {
        mPassphraseEditText.setRawInputType(
            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
      }

      mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());

      AlertDialog dialog = alert.create();
      dialog.setButton(
          DialogInterface.BUTTON_POSITIVE,
          activity.getString(R.string.btn_unlock),
          (DialogInterface.OnClickListener) null);

      return dialog;
    }
コード例 #9
0
ファイル: ContactHelper.java プロジェクト: BenEdridge/apg
  private static void writeKeysToNormalContacts(Context context, ContentResolver resolver) {
    // delete raw contacts flagged for deletion by user so they can be reinserted
    deleteFlaggedNormalRawContacts(resolver);

    Set<Long> deletedKeys = getRawContactMasterKeyIds(resolver);

    // 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);
        KeyRing.UserId userIdSplit = KeyRing.splitUserId(cursor.getString(INDEX_USER_ID));
        boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0;
        boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
        boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;

        Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);

        deletedKeys.remove(masterKeyId);

        // get raw contact to this master key id
        long rawContactId = findRawContactId(resolver, masterKeyId);
        Log.d(Constants.TAG, "rawContactId: " + rawContactId);

        ArrayList<ContentProviderOperation> ops = new ArrayList<>();

        // 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);
          }
        } else if (userIdSplit.name != null) {

          // Create a new rawcontact with corresponding key if it does not exist yet
          if (rawContactId == -1) {
            Log.d(Constants.TAG, "Insert new raw contact with masterKeyId " + masterKeyId);

            insertContact(ops, context, masterKeyId);
            writeContactKey(ops, context, rawContactId, masterKeyId, userIdSplit.name);
          }

          // We always update the display name (which is derived from primary user id)
          // and email addresses from user id
          writeContactDisplayName(ops, rawContactId, userIdSplit.name);
          writeContactEmail(ops, resolver, rawContactId, masterKeyId);
          try {
            resolver.applyBatch(ContactsContract.AUTHORITY, ops);
          } catch (Exception e) {
            Log.w(Constants.TAG, e);
          }
        }
      }
      cursor.close();
    }

    // Delete master key ids that are no longer present in OK
    for (Long masterKeyId : deletedKeys) {
      Log.d(Constants.TAG, "Delete raw contact with masterKeyId " + masterKeyId);
      deleteRawContactByMasterKeyId(resolver, masterKeyId);
    }
  }