diff options
Diffstat (limited to 'OpenKeychain/src/main/java')
36 files changed, 677 insertions, 190 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java index e796bdc91..40dcbd78d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + package org.sufficientlysecure.keychain.operations; import android.content.Context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 2e9551826..ebf0dc70b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + package org.sufficientlysecure.keychain.operations; import android.content.Context; @@ -20,6 +37,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; +import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -191,6 +209,9 @@ public class CertifyOperation extends BaseOperation { } log.add(LogType.MSG_CRT_SUCCESS, 0); + //since only verified keys are synced to contacts, we need to initiate a sync now + ContactSyncAdapterService.requestSync(); + return new CertifyResult(CertifyResult.RESULT_OK, log, certifyOk, certifyError, uploadOk, uploadError); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java index 124dd1aaf..5ef04ab05 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + package org.sufficientlysecure.keychain.operations; import android.content.Context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index 7f14b08d9..bcd842dd0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + package org.sufficientlysecure.keychain.operations; import android.content.Context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java index 783b32477..fd86d4b92 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + package org.sufficientlysecure.keychain.operations; import android.content.Context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java index e98f6b150..b5552a40d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + package org.sufficientlysecure.keychain.operations; import android.content.Context; @@ -48,6 +65,7 @@ public class SignEncryptOperation extends BaseOperation { byte[] inputBytes = input.getBytes(); byte[] outputBytes = null; + int total = inputBytes != null ? 1 : inputUris.size(), count = 0; ArrayList<PgpSignEncryptResult> results = new ArrayList<>(); do { @@ -104,7 +122,7 @@ public class SignEncryptOperation extends BaseOperation { } PgpSignEncryptOperation op = new PgpSignEncryptOperation(mContext, mProviderHelper, - new ProgressScaler(), mCancelled); + new ProgressScaler(mProgressable, 100 * count / total, 100 * ++count / total, 100), mCancelled); PgpSignEncryptResult result = op.execute(input, inputData, outStream); results.add(result); log.add(result, 2); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 5856589c4..6af5f4217 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -123,6 +123,7 @@ public class KeychainContract { public static final String HAS_SIGN = "has_sign"; public static final String HAS_CERTIFY = "has_certify"; public static final String HAS_AUTHENTICATE = "has_authenticate"; + public static final String HAS_DUPLICATE_USER_ID = "has_duplicate_user_id"; public static final String PUBKEY_DATA = "pubkey_data"; public static final String PRIVKEY_DATA = "privkey_data"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 4ccfc3cd9..1351e0cbc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -273,7 +273,15 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(KeyRings.EXPIRY, Tables.KEYS + "." + Keys.EXPIRY); projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM); projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT); - projectionMap.put(KeyRings.USER_ID, UserPackets.USER_ID); + projectionMap.put(KeyRings.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID); + projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID, + "(SELECT COUNT (*) FROM " + Tables.USER_PACKETS + " AS dups" + + " WHERE dups." + UserPackets.MASTER_KEY_ID + + " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " AND dups." + UserPackets.RANK + " = 0" + + " AND dups." + UserPackets.USER_ID + + " = "+ Tables.USER_PACKETS + "." + UserPackets.USER_ID + + ") AS " + KeyRings.HAS_DUPLICATE_USER_ID); projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED); projectionMap.put(KeyRings.PUBKEY_DATA, Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.KEY_RING_DATA diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java index 940ae27d0..4bb64bcaa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java @@ -28,8 +28,8 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; @@ -106,8 +106,8 @@ public class AccountSettingsFragment extends Fragment { case REQUEST_CODE_CREATE_KEY: { if (resultCode == Activity.RESULT_OK) { if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { - SaveKeyringResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); - mSelectKeySpinner.setSelectedKeyId(result.mRingMasterKeyId); + EditKeyResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + mSelectKeySpinner.setSelectedKeyId(result.mMasterKeyId); } else { Log.e(Constants.TAG, "missing result!"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java index 8578bb384..5ec47f4c9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java @@ -127,9 +127,9 @@ public class SelectSignKeyIdActivity extends BaseActivity { case REQUEST_CODE_CREATE_KEY: { if (resultCode == Activity.RESULT_OK) { if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { -// SaveKeyringResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); // TODO: select? -// mSelectKeySpinner.setSelectedKeyId(result.mRingMasterKeyId); +// EditKeyResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); +// mSelectKeySpinner.setSelectedKeyId(result.mMasterKeyId); } else { Log.e(Constants.TAG, "missing result!"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index d95701458..bf6a62782 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -60,6 +60,7 @@ import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -126,8 +127,21 @@ public class KeychainIntentService extends IntentService implements Progressable public static final String SOURCE = "source"; // possible targets: - public static final int IO_BYTES = 1; - public static final int IO_URI = 2; + public static enum IOType { + UNKNOWN, + BYTES, + URI; + + private static final IOType[] values = values(); + + public static IOType fromInt(int n) { + if(n < 0 || n >= values.length) { + return UNKNOWN; + } else { + return values[n]; + } + } + } // encrypt public static final String ENCRYPT_DECRYPT_INPUT_URI = "input_uri"; @@ -244,7 +258,7 @@ public class KeychainIntentService extends IntentService implements Progressable CertifyResult result = op.certify(parcel, keyServerUri); // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + sendMessageToHandler(MessageStatus.OKAY, result); break; } @@ -259,7 +273,7 @@ public class KeychainIntentService extends IntentService implements Progressable } // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + sendMessageToHandler(MessageStatus.OKAY, result); break; } @@ -288,7 +302,7 @@ public class KeychainIntentService extends IntentService implements Progressable DecryptVerifyResult decryptVerifyResult = builder.build().execute(); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, decryptVerifyResult); + sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult); } catch (Exception e) { sendErrorToHandler(e); } @@ -349,7 +363,7 @@ public class KeychainIntentService extends IntentService implements Progressable } // kind of awkward, but this whole class wants to pull bytes out of “data” - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); + data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal()); data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, messageBytes); InputData inputData = createDecryptInputData(data); @@ -386,7 +400,7 @@ public class KeychainIntentService extends IntentService implements Progressable resultData.putString(KeychainIntentServiceHandler.KEYBASE_PROOF_URL, prover.getProofUrl()); resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl()); resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_LABEL, prover.getPresenceLabel()); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); + sendMessageToHandler(MessageStatus.OKAY, resultData); } catch (Exception e) { sendErrorToHandler(e); } @@ -429,7 +443,7 @@ public class KeychainIntentService extends IntentService implements Progressable Log.logDebugBundle(resultData, "resultData"); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); + sendMessageToHandler(MessageStatus.OKAY, resultData); } catch (Exception e) { sendErrorToHandler(e); } @@ -447,7 +461,7 @@ public class KeychainIntentService extends IntentService implements Progressable DeleteResult result = op.execute(masterKeyIds, isSecret); // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + sendMessageToHandler(MessageStatus.OKAY, result); break; } @@ -462,7 +476,7 @@ public class KeychainIntentService extends IntentService implements Progressable EditKeyResult result = op.execute(saveParcel, passphrase); // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + sendMessageToHandler(MessageStatus.OKAY, result); break; } @@ -476,7 +490,7 @@ public class KeychainIntentService extends IntentService implements Progressable PromoteKeyResult result = op.execute(keyRingId); // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + sendMessageToHandler(MessageStatus.OKAY, result); break; } @@ -500,7 +514,7 @@ public class KeychainIntentService extends IntentService implements Progressable } // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + sendMessageToHandler(MessageStatus.OKAY, result); break; } @@ -521,7 +535,7 @@ public class KeychainIntentService extends IntentService implements Progressable : importExportOperation.importKeyRings(cache, keyServer); // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + sendMessageToHandler(MessageStatus.OKAY, result); break; @@ -537,7 +551,7 @@ public class KeychainIntentService extends IntentService implements Progressable SignEncryptResult result = op.execute(inputParcel); // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + sendMessageToHandler(MessageStatus.OKAY, result); break; } @@ -560,7 +574,7 @@ public class KeychainIntentService extends IntentService implements Progressable throw new PgpGeneralException("Unable to export key to selected server"); } - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); + sendMessageToHandler(MessageStatus.OKAY); } catch (Exception e) { sendErrorToHandler(e); } @@ -582,7 +596,7 @@ public class KeychainIntentService extends IntentService implements Progressable private void sendProofError(String msg) { Bundle bundle = new Bundle(); bundle.putString(KeychainIntentServiceHandler.DATA_ERROR, msg); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, bundle); + sendMessageToHandler(MessageStatus.OKAY, bundle); } private void sendErrorToHandler(Exception e) { @@ -599,14 +613,14 @@ public class KeychainIntentService extends IntentService implements Progressable Bundle data = new Bundle(); data.putString(KeychainIntentServiceHandler.DATA_ERROR, message); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_EXCEPTION, null, data); + sendMessageToHandler(MessageStatus.EXCEPTION, null, data); } - private void sendMessageToHandler(Integer arg1, Integer arg2, Bundle data) { + private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) { Message msg = Message.obtain(); assert msg != null; - msg.arg1 = arg1; + msg.arg1 = status.ordinal(); if (arg2 != null) { msg.arg2 = arg2; } @@ -623,18 +637,18 @@ public class KeychainIntentService extends IntentService implements Progressable } } - private void sendMessageToHandler(Integer arg1, OperationResult data) { + private void sendMessageToHandler(MessageStatus status, OperationResult data) { Bundle bundle = new Bundle(); bundle.putParcelable(OperationResult.EXTRA_RESULT, data); - sendMessageToHandler(arg1, null, bundle); + sendMessageToHandler(status, null, bundle); } - private void sendMessageToHandler(Integer arg1, Bundle data) { - sendMessageToHandler(arg1, null, data); + private void sendMessageToHandler(MessageStatus status, Bundle data) { + sendMessageToHandler(status, null, data); } - private void sendMessageToHandler(Integer arg1) { - sendMessageToHandler(arg1, null, null); + private void sendMessageToHandler(MessageStatus status) { + sendMessageToHandler(status, null, null); } /** @@ -651,7 +665,7 @@ public class KeychainIntentService extends IntentService implements Progressable data.putInt(KeychainIntentServiceHandler.DATA_PROGRESS, progress); data.putInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX, max); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS, null, data); + sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data); } public void setProgress(int resourceId, int progress, int max) { @@ -664,7 +678,7 @@ public class KeychainIntentService extends IntentService implements Progressable @Override public void setPreventCancel() { - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_PREVENT_CANCEL); + sendMessageToHandler(MessageStatus.PREVENT_CANCEL); } private InputData createDecryptInputData(Bundle data) throws IOException, PgpGeneralException { @@ -673,35 +687,37 @@ public class KeychainIntentService extends IntentService implements Progressable private InputData createCryptInputData(Bundle data, String bytesName) throws PgpGeneralException, IOException { int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); - switch (source) { - case IO_BYTES: /* encrypting bytes directly */ + IOType type = IOType.fromInt(source); + switch (type) { + case BYTES: /* encrypting bytes directly */ byte[] bytes = data.getByteArray(bytesName); return new InputData(new ByteArrayInputStream(bytes), bytes.length); - case IO_URI: /* encrypting content uri */ + case URI: /* encrypting content uri */ Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_INPUT_URI); // InputStream return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0)); default: - throw new PgpGeneralException("No target choosen!"); + throw new PgpGeneralException("No target chosen!"); } } private OutputStream createCryptOutputStream(Bundle data) throws PgpGeneralException, FileNotFoundException { int target = data.getInt(TARGET); - switch (target) { - case IO_BYTES: + IOType type = IOType.fromInt(target); + switch (type) { + case BYTES: return new ByteArrayOutputStream(); - case IO_URI: + case URI: Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_OUTPUT_URI); return getContentResolver().openOutputStream(providerUri); default: - throw new PgpGeneralException("No target choosen!"); + throw new PgpGeneralException("No target chosen!"); } } @@ -711,12 +727,13 @@ public class KeychainIntentService extends IntentService implements Progressable private void finalizeCryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream, String bytesName) { int target = data.getInt(TARGET); - switch (target) { - case IO_BYTES: + IOType type = IOType.fromInt(target); + switch (type) { + case BYTES: byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); resultData.putByteArray(bytesName, output); break; - case IO_URI: + case URI: // nothing, output was written, just send okay and verification bundle break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java index 635fe86f1..bd047518d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java @@ -32,11 +32,25 @@ import org.sufficientlysecure.keychain.util.Log; public class KeychainIntentServiceHandler extends Handler { - // possible messages send from this service to handler on ui - public static final int MESSAGE_OKAY = 1; - public static final int MESSAGE_EXCEPTION = 2; - public static final int MESSAGE_UPDATE_PROGRESS = 3; - public static final int MESSAGE_PREVENT_CANCEL = 4; + // possible messages sent from this service to handler on ui + public static enum MessageStatus{ + UNKNOWN, + OKAY, + EXCEPTION, + UPDATE_PROGRESS, + PREVENT_CANCEL; + + private static final MessageStatus[] values = values(); + + public static MessageStatus fromInt(int n) + { + if(n < 0 || n >= values.length) { + return UNKNOWN; + } else { + return values[n]; + } + } + } // possible data keys for messages public static final String DATA_ERROR = "error"; @@ -103,13 +117,14 @@ public class KeychainIntentServiceHandler extends Handler { return; } - switch (message.arg1) { - case MESSAGE_OKAY: + MessageStatus status = MessageStatus.fromInt(message.arg1); + switch (status) { + case OKAY: mProgressDialogFragment.dismissAllowingStateLoss(); break; - case MESSAGE_EXCEPTION: + case EXCEPTION: mProgressDialogFragment.dismissAllowingStateLoss(); // show error from service @@ -121,7 +136,7 @@ public class KeychainIntentServiceHandler extends Handler { break; - case MESSAGE_UPDATE_PROGRESS: + case UPDATE_PROGRESS: if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) { // update progress from service @@ -139,7 +154,7 @@ public class KeychainIntentServiceHandler extends Handler { break; - case MESSAGE_PREVENT_CANCEL: + case PREVENT_CANCEL: mProgressDialogFragment.setPreventCancel(true); break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index e1467ec61..1e1bd32c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -395,7 +395,7 @@ public class CertifyKeyFragment extends LoaderFragment // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { Bundle data = message.getData(); CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java index f0ef8b9ef..c55aad1f9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java @@ -57,7 +57,7 @@ public class ConsolidateDialogActivity extends FragmentActivity { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { /* don't care about the results (for now?) // get returned data bundle diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index 4853c61c5..ae42c891d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -214,7 +214,7 @@ public class CreateKeyFinalFragment extends Fragment { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle Bundle returnData = message.getData(); if (returnData == null) { @@ -284,7 +284,7 @@ public class CreateKeyFinalFragment extends Fragment { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // TODO: upload operation needs a result! // TODO: then combine these results //if (result.getResult() == OperationResultParcel.RESULT_OK) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java index 5606523be..c808557a6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -182,10 +183,10 @@ public class DecryptFilesFragment extends DecryptFragment { // data Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); - data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); + data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal()); data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri); - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); + data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal()); data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri); data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase); @@ -200,7 +201,7 @@ public class DecryptFilesFragment extends DecryptFragment { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle Bundle returnData = message.getData(); @@ -256,10 +257,10 @@ public class DecryptFilesFragment extends DecryptFragment { // data Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); - data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); + data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal()); data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri); - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); + data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal()); data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri); data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase); @@ -274,7 +275,7 @@ public class DecryptFilesFragment extends DecryptFragment { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle Bundle returnData = message.getData(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java index a15b23c06..1b34f6bf0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; @@ -158,7 +159,7 @@ public class DecryptTextFragment extends DecryptFragment { intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); // data - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); + data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal()); data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes()); data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase); data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey); @@ -172,7 +173,7 @@ public class DecryptTextFragment extends DecryptFragment { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle Bundle returnData = message.getData(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 1e9a90fc8..8b9323f19 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -605,7 +605,7 @@ public class EditKeyFragment extends LoaderFragment implements // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle Bundle returnData = message.getData(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index ed5920cde..35dfcb87c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -128,7 +128,7 @@ public abstract class EncryptActivity extends BaseActivity { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { SignEncryptResult result = message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java index d95b5cda3..b862d5b11 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -36,7 +36,6 @@ import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ShareHelper; import java.util.ArrayList; @@ -177,22 +176,36 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi } @Override - public void onEncryptSuccess(SignEncryptResult result) { + public void onEncryptSuccess(final SignEncryptResult result) { if (mDeleteAfterEncrypt) { - for (Uri inputUri : mInputUris) { - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUri); - deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); - } + final Uri[] inputUris = mInputUris.toArray(new Uri[mInputUris.size()]); + DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUris); + deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() { + + @Override + public void onDeleted() { + if (mShareAfterEncrypt) { + // Share encrypted message/file + startActivity(sendWithChooserExcludingEncrypt()); + } else { + // Save encrypted file + result.createNotify(EncryptFilesActivity.this).show(); + } + } + + }); + deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); + mInputUris.clear(); notifyUpdate(); - } - - if (mShareAfterEncrypt) { - // Share encrypted message/file - startActivity(sendWithChooserExcludingEncrypt()); } else { - // Save encrypted file - result.createNotify(EncryptFilesActivity.this).show(); + if (mShareAfterEncrypt) { + // Share encrypted message/file + startActivity(sendWithChooserExcludingEncrypt()); + } else { + // Save encrypted file + result.createNotify(EncryptFilesActivity.this).show(); + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index ace58b165..48737d223 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -114,6 +114,13 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt return; } + if (mEncryptInterface.getInputUris().contains(inputUri)) { + Notify.showNotify(getActivity(), + getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)), + Notify.Style.ERROR); + return; + } + mEncryptInterface.getInputUris().add(inputUri); mEncryptInterface.notifyUpdate(); mSelectedFiles.requestFocus(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 71f6fd4bf..d51e2c7fc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -302,7 +302,7 @@ public class ImportKeysActivity extends BaseActivity { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle Bundle returnData = message.getData(); if (returnData == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java index 948da94d8..cc8b47971 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -222,7 +222,7 @@ public class ImportKeysProxyActivity extends FragmentActivity { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle Bundle returnData = message.getData(); if (returnData == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 43d893fa6..8c34efba2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -270,7 +270,8 @@ public class KeyListFragment extends LoaderFragment KeyRings.IS_REVOKED, KeyRings.IS_EXPIRED, KeyRings.VERIFIED, - KeyRings.HAS_ANY_SECRET + KeyRings.HAS_ANY_SECRET, + KeyRings.HAS_DUPLICATE_USER_ID, }; static final int INDEX_MASTER_KEY_ID = 1; @@ -279,6 +280,7 @@ public class KeyListFragment extends LoaderFragment static final int INDEX_IS_EXPIRED = 4; static final int INDEX_VERIFIED = 5; static final int INDEX_HAS_ANY_SECRET = 6; + static final int INDEX_HAS_DUPLICATE_USER_ID = 7; static final String ORDER = KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC"; @@ -552,7 +554,7 @@ public class KeyListFragment extends LoaderFragment // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle Bundle returnData = message.getData(); if (returnData == null) { @@ -707,6 +709,7 @@ public class KeyListFragment extends LoaderFragment boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0; + boolean hasDuplicate = cursor.getInt(INDEX_HAS_DUPLICATE_USER_ID) == 1; h.mMasterKeyId = masterKeyId; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java index d1df2906d..d0cea5f05 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java @@ -132,7 +132,7 @@ public class SafeSlingerActivity extends BaseActivity { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle Bundle returnData = message.getData(); if (returnData == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index e19793fd5..4fb2074a5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -113,7 +113,7 @@ public class UploadKeyActivity extends BaseActivity { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { Toast.makeText(UploadKeyActivity.this, R.string.msg_crt_upload_success, Toast.LENGTH_SHORT).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index c94b29bac..b25f6bbf2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -25,6 +25,7 @@ import android.annotation.TargetApi; import android.app.Activity; import android.app.ActivityOptions; import android.content.Intent; +import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; @@ -72,6 +73,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -373,6 +375,11 @@ public class ViewKeyActivity extends BaseActivity implements @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void invokeNfcBeam() { + // Check if device supports NFC + if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) { + Notify.createNotify(this, R.string.no_nfc_support, Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + return; + } // Check for available NFC Adapter mNfcAdapter = NfcAdapter.getDefaultAdapter(this); if (mNfcAdapter == null || !mNfcAdapter.isEnabled()) { @@ -429,7 +436,7 @@ public class ViewKeyActivity extends BaseActivity implements // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { Bundle data = message.getData(); CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); @@ -486,7 +493,7 @@ public class ViewKeyActivity extends BaseActivity implements Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { setResult(RESULT_CANCELED); finish(); } @@ -595,7 +602,7 @@ public class ViewKeyActivity extends BaseActivity implements // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle Bundle returnData = message.getData(); 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 bb3df2541..c3a8d60f8 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> { @@ -53,7 +55,7 @@ public class ViewKeyFragment extends LoaderFragment implements //private ListView mLinkedSystemContact; boolean mIsSecret = false; - private String mName; + boolean mSystemContactLoaded = false; LinearLayout mSystemContactLayout; ImageView mSystemContactPicture; @@ -119,28 +121,48 @@ 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 name * @param masterKeyId */ - private void loadLinkedSystemContact(String name, final long masterKeyId) { + private void loadLinkedSystemContact(final long masterKeyId) { final Context context = mSystemContactName.getContext(); final ContentResolver resolver = context.getContentResolver(); - final long contactId = ContactHelper.findContactId(resolver, masterKeyId); + 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 && mainProfileNames.size() > 0) { + contactName = mainProfileNames.get(0); + } - if (contactId != -1) {//contact exists for given master key - mSystemContactName.setText(name); + } else { + contactId = ContactHelper.findContactId(resolver, masterKeyId); + contactName = ContactHelper.getContactName(resolver, contactId); + } - Bitmap picture = ContactHelper.loadPhotoByMasterKeyId(resolver, masterKeyId, true); + if (contactName != null) {//contact name exists for given master key + mSystemContactName.setText(contactName); + + 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; } } @@ -240,11 +262,11 @@ public class ViewKeyFragment extends LoaderFragment implements if (data.moveToFirst()) { mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; - if (mName == null) {//to ensure we load the linked system contact only once - String[] mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID)); - mName = mainUserId[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(mName, masterKeyId); + loadLinkedSystemContact(masterKeyId); } // load user ids after we know if it's a secret key mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java index d22f01a48..e20796f8f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java @@ -368,7 +368,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { Bundle returnData = message.getData(); String msg = returnData.getString(KeychainIntentServiceHandler.DATA_MESSAGE); SpannableStringBuilder ssb = new SpannableStringBuilder(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java index c4b437593..bd4e5577b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Dialog; -import android.content.ContentResolver; import android.content.DialogInterface; import android.net.Uri; import android.os.Build; @@ -34,18 +33,22 @@ import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; public class DeleteFileDialogFragment extends DialogFragment { - private static final String ARG_DELETE_URI = "delete_uri"; + private static final String ARG_DELETE_URIS = "delete_uris"; + + private OnDeletedListener onDeletedListener; /** * Creates new instance of this delete file dialog fragment */ - public static DeleteFileDialogFragment newInstance(Uri deleteUri) { + public static DeleteFileDialogFragment newInstance(Uri... deleteUris) { DeleteFileDialogFragment frag = new DeleteFileDialogFragment(); Bundle args = new Bundle(); - args.putParcelable(ARG_DELETE_URI, deleteUri); + args.putParcelableArray(ARG_DELETE_URIS, deleteUris); frag.setArguments(args); @@ -59,12 +62,21 @@ public class DeleteFileDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final FragmentActivity activity = getActivity(); - final Uri deleteUri = getArguments().getParcelable(ARG_DELETE_URI); - final String deleteFilename = FileHelper.getFilename(getActivity(), deleteUri); + final Uri[] deleteUris = (Uri[]) getArguments().getParcelableArray(ARG_DELETE_URIS); + + final StringBuilder deleteFileNames = new StringBuilder(); + //Retrieving file names after deletion gives unexpected results + final HashMap<Uri, String> deleteFileNameMap = new HashMap<>(); + for (Uri deleteUri : deleteUris) { + String deleteFileName = FileHelper.getFilename(getActivity(), deleteUri); + deleteFileNames.append('\n').append(deleteFileName); + deleteFileNameMap.put(deleteUri, deleteFileName); + } CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); - alert.setMessage(this.getString(R.string.file_delete_confirmation, deleteFilename)); + alert.setTitle(getString(R.string.file_delete_confirmation_title)); + alert.setMessage(getString(R.string.file_delete_confirmation, deleteFileNames.toString())); alert.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { @@ -72,43 +84,55 @@ public class DeleteFileDialogFragment extends DialogFragment { public void onClick(DialogInterface dialog, int id) { dismiss(); - // NOTE: Use Toasts, not Snackbars. When sharing to another application snackbars - // would not show up! + ArrayList<String> failedFileNameList = new ArrayList<>(); + + for (Uri deleteUri : deleteUris) { + // Use DocumentsContract on Android >= 4.4 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + try { + if (DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri)) { + continue; + } + } catch (Exception e) { + Log.d(Constants.TAG, "Catched Exception, can happen when delete is not supported!", e); + } + } - // Use DocumentsContract on Android >= 4.4 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { try { - if (DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri)) { - Toast.makeText(getActivity(), getActivity().getString(R.string.file_delete_successful, - deleteFilename), Toast.LENGTH_LONG).show(); - return; + if (getActivity().getContentResolver().delete(deleteUri, null, null) > 0) { + continue; } - } catch (UnsupportedOperationException e) { - Log.d(Constants.TAG, "Catched UnsupportedOperationException, can happen when delete is not supported!", e); + } catch (Exception e) { + Log.d(Constants.TAG, "Catched Exception, can happen when delete is not supported!", e); } - } - try { - if (getActivity().getContentResolver().delete(deleteUri, null, null) > 0) { - Toast.makeText(getActivity(), getActivity().getString(R.string.file_delete_successful, - deleteFilename), Toast.LENGTH_LONG).show(); - return; + // some Uri's a ContentResolver fails to delete is handled by the java.io.File's delete + // via the path of the Uri + if (new File(deleteUri.getPath()).delete()) { + continue; } - } catch (UnsupportedOperationException e) { - Log.d(Constants.TAG, "Catched UnsupportedOperationException, can happen when delete is not supported!", e); + + // Note: We can't delete every file... + failedFileNameList.add(deleteFileNameMap.get(deleteUri)); } - // some Uri's a ContentResolver fails to delete is handled by the java.io.File's delete - // via the path of the Uri - if (new File(deleteUri.getPath()).delete()) { - Toast.makeText(getActivity(), getActivity().getString(R.string.file_delete_successful, - deleteFilename), Toast.LENGTH_LONG).show(); - return; + StringBuilder failedFileNames = new StringBuilder(); + if (!failedFileNameList.isEmpty()) { + for (String failedFileName : failedFileNameList) { + failedFileNames.append('\n').append(failedFileName); + } + failedFileNames.append('\n').append(getActivity().getString(R.string.error_file_delete_failed)); } - // Note: We can't delete every file... - Toast.makeText(getActivity(), getActivity().getString(R.string.error_file_delete_failed, - deleteFilename), Toast.LENGTH_LONG).show(); + // NOTE: Use Toasts, not Snackbars. When sharing to another application snackbars + // would not show up! + Toast.makeText(getActivity(), getActivity().getString(R.string.file_delete_successful, + deleteUris.length - failedFileNameList.size(), deleteUris.length, failedFileNames.toString()), + Toast.LENGTH_LONG).show(); + + if (onDeletedListener != null) { + onDeletedListener.onDeleted(); + } } }); alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @@ -120,4 +144,18 @@ public class DeleteFileDialogFragment extends DialogFragment { return alert.show(); } + + public void setOnDeletedListener(OnDeletedListener onDeletedListener) { + this.onDeletedListener = onDeletedListener; + } + + /** + * Callback for performing tasks after the deletion of files + */ + public interface OnDeletedListener { + + public void onDeleted(); + + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java index 32789d53b..20f20c32e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java @@ -142,7 +142,7 @@ public class DeleteKeyDialogFragment extends DialogFragment { public void handleMessage(Message message) { super.handleMessage(message); // handle messages by standard KeychainIntentServiceHandler first - if (message.arg1 == MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { try { Message msg = Message.obtain(); msg.copyFrom(message); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java index 6dd254aa1..fb6b84f58 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -68,7 +68,9 @@ public class CertifyKeySpinner extends KeySpinner { KeychainContract.KeyRings.IS_REVOKED, KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.HAS_CERTIFY, - KeychainContract.KeyRings.HAS_ANY_SECRET + KeychainContract.KeyRings.HAS_ANY_SECRET, + KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID, + KeychainContract.KeyRings.CREATION }; String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND " diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index c8eceea50..ab5b02301 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -25,6 +25,7 @@ import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.support.v7.internal.widget.TintSpinner; +import android.text.format.DateFormat; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -41,6 +42,10 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + /** * Use TintSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon. * Related: http://stackoverflow.com/a/27713090 @@ -139,11 +144,12 @@ public abstract class KeySpinner extends TintSpinner implements LoaderManager.Lo protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter { private CursorAdapter inner; private int mIndexUserId; - private int mIndexKeyId; + private int mIndexDuplicate; private int mIndexMasterKeyId; + private int mIndexCreationDate; public SelectKeyAdapter() { - inner = new CursorAdapter(null, null, 0) { + inner = new CursorAdapter(getContext(), null, 0) { @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return View.inflate(getContext(), R.layout.keyspinner_item, null); @@ -154,12 +160,26 @@ public abstract class KeySpinner extends TintSpinner implements LoaderManager.Lo TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name); ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status); TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); - TextView vKeyId = (TextView) view.findViewById(R.id.keyspinner_key_id); + TextView vDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate); String[] userId = KeyRing.splitUserId(cursor.getString(mIndexUserId)); vKeyName.setText(userId[2] == null ? userId[0] : (userId[0] + " (" + userId[2] + ")")); vKeyEmail.setText(userId[1]); - vKeyId.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(getContext(), cursor.getLong(mIndexKeyId))); + + boolean duplicate = cursor.getLong(mIndexDuplicate) > 0; + if (duplicate) { + Date creationDate = new Date(cursor.getLong(mIndexCreationDate) * 1000); + Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + creationCal.setTime(creationDate); + // convert from UTC to time zone of device + creationCal.setTimeZone(TimeZone.getDefault()); + + vDuplicate.setText(context.getString(R.string.label_creation) + ": " + + DateFormat.getDateFormat(context).format(creationCal.getTime())); + vDuplicate.setVisibility(View.VISIBLE); + } else { + vDuplicate.setVisibility(View.GONE); + } boolean valid = setStatus(getContext(), cursor, vKeyStatus); setItemEnabled(view, valid); @@ -181,18 +201,18 @@ public abstract class KeySpinner extends TintSpinner implements LoaderManager.Lo TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name); ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status); TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); - TextView vKeyId = (TextView) view.findViewById(R.id.keyspinner_key_id); + TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate); if (enabled) { vKeyName.setTextColor(Color.BLACK); vKeyEmail.setTextColor(Color.BLACK); - vKeyId.setTextColor(Color.BLACK); + vKeyDuplicate.setTextColor(Color.BLACK); vKeyStatus.setVisibility(View.GONE); view.setClickable(false); } else { vKeyName.setTextColor(Color.GRAY); vKeyEmail.setTextColor(Color.GRAY); - vKeyId.setTextColor(Color.GRAY); + vKeyDuplicate.setTextColor(Color.GRAY); vKeyStatus.setVisibility(View.VISIBLE); // this is a HACK. the trick is, if the element itself is clickable, the // click is not passed on to the view list @@ -203,9 +223,10 @@ public abstract class KeySpinner extends TintSpinner implements LoaderManager.Lo public Cursor swapCursor(Cursor newCursor) { if (newCursor == null) return inner.swapCursor(null); - mIndexKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.KEY_ID); + mIndexDuplicate = newCursor.getColumnIndex(KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID); mIndexUserId = newCursor.getColumnIndex(KeychainContract.KeyRings.USER_ID); mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID); + mIndexCreationDate = newCursor.getColumnIndex(KeychainContract.KeyRings.CREATION); if (newCursor.moveToFirst()) { do { if (newCursor.getLong(mIndexMasterKeyId) == mSelectedKeyId) { @@ -257,19 +278,17 @@ public abstract class KeySpinner extends TintSpinner implements LoaderManager.Lo TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name); ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status); TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); - TextView vKeyId = (TextView) view.findViewById(R.id.keyspinner_key_id); + TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate); vKeyName.setText(R.string.choice_none); vKeyEmail.setVisibility(View.GONE); - vKeyId.setVisibility(View.GONE); + vKeyDuplicate.setVisibility(View.GONE); vKeyStatus.setVisibility(View.GONE); setItemEnabled(view, true); } else { view = inner.getView(position - 1, convertView, parent); TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); - TextView vKeyId = (TextView) view.findViewById(R.id.keyspinner_key_id); vKeyEmail.setVisibility(View.VISIBLE); - vKeyId.setVisibility(View.VISIBLE); } return view; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java index 10327a6a4..df7347fa4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java @@ -59,7 +59,9 @@ public class SignKeySpinner extends KeySpinner { KeychainContract.KeyRings.IS_REVOKED, KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.HAS_SIGN, - KeychainContract.KeyRings.HAS_ANY_SECRET + KeychainContract.KeyRings.HAS_ANY_SECRET, + KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID, + KeychainContract.KeyRings.CREATION }; String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1"; 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 1413273e8..6efc0a5ea 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,55 @@ 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 && profileCursor.getCount() != 0 && profileCursor.moveToNext()) { + long contactId = profileCursor.getLong(0); + profileCursor.close(); + return contactId; + } + else { + if(profileCursor != null) { + profileCursor.close(); + } + return -1; + } + } + + /** + * 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 +318,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 @@ -296,6 +345,34 @@ public class ContactHelper { return contactId; } + /** + * Returns the display name of the system contact associated with contactId, null if the + * contact does not exist + * + * @param resolver + * @param contactId + * @return primary display name of system contact associated with contactId, null if it does + * not exist + */ + public static String getContactName(ContentResolver resolver, long contactId) { + String contactName = null; + Cursor raw = resolver.query(ContactsContract.Contacts.CONTENT_URI, + new String[]{ + ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + }, + ContactsContract.Contacts._ID + "=?", + new String[]{//"0" for "not deleted" + Long.toString(contactId) + }, null); + if (raw != null) { + if (raw.moveToNext()) { + contactName = raw.getString(0); + } + raw.close(); + } + return contactName; + } + public static Bitmap getCachedPhotoByMasterKeyId(ContentResolver contentResolver, long masterKeyId) { if (masterKeyId == -1) { return null; @@ -333,46 +410,47 @@ public class ContactHelper { KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.IS_EXPIRED, - KeychainContract.KeyRings.IS_REVOKED}; + KeychainContract.KeyRings.IS_REVOKED, + KeychainContract.KeyRings.VERIFIED, + 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; public static final int INDEX_IS_EXPIRED = 2; 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 */ public static void writeKeysToContacts(Context context) { ContentResolver resolver = context.getContentResolver(); - Set<Long> deletedKeys = getRawContactMasterKeyIds(resolver); if (Constants.DEBUG_SYNC_REMOVE_CONTACTS) { debugDeleteRawContacts(resolver); } -// ContentProviderClient client = resolver.acquireContentProviderClient(ContactsContract.AUTHORITY_URI); -// ContentValues values = new ContentValues(); -// Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE); -// values.put(ContactsContract.Settings.ACCOUNT_NAME, account.name); -// values.put(ContactsContract.Settings.ACCOUNT_TYPE, account.type); -// values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, true); -// try { -// client.insert(ContactsContract.Settings.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(), values); -// } catch (RemoteException e) { -// e.printStackTrace(); -// } - - // Load all Keys from OK - Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), KEYS_TO_CONTACT_PROJECTION, - null, null, null); + writeKeysToMainProfileContact(context, 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); String[] userIdSplit = KeyRing.splitUserId(cursor.getString(INDEX_USER_ID)); - String keyIdShort = KeyFormattingUtils.convertKeyIdToHexShort(cursor.getLong(INDEX_MASTER_KEY_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); @@ -384,9 +462,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) { - Log.d(Constants.TAG, "Expired or revoked: 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); } @@ -397,7 +477,7 @@ public class ContactHelper { Log.d(Constants.TAG, "Insert new raw contact with masterKeyId " + masterKeyId); insertContact(ops, context, masterKeyId); - writeContactKey(ops, context, rawContactId, masterKeyId, keyIdShort); + writeContactKey(ops, context, rawContactId, masterKeyId, userIdSplit[0]); } // We always update the display name (which is derived from primary user id) @@ -422,43 +502,165 @@ 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) { + 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; + String[] userIdSplit = KeyRing.splitUserId(cursor.getString(INDEX_USER_ID)); + + if (!isExpired && !isRevoked && userIdSplit[0] != 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[0]); + + 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) }); @@ -487,6 +689,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 @@ -528,10 +752,10 @@ public class ContactHelper { * This creates the link to OK in contact details */ private static void writeContactKey(ArrayList<ContentProviderOperation> ops, Context context, long rawContactId, - long masterKeyId, String keyIdShort) { + long masterKeyId, String keyName) { ops.add(referenceRawContact(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI), rawContactId) .withValue(ContactsContract.Data.MIMETYPE, Constants.CUSTOM_CONTACT_DATA_MIME_TYPE) - .withValue(ContactsContract.Data.DATA1, context.getString(R.string.contact_show_key, keyIdShort)) + .withValue(ContactsContract.Data.DATA1, context.getString(R.string.contact_show_key, keyName)) .withValue(ContactsContract.Data.DATA2, masterKeyId) .build()); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java index 1d78ed80e..cda5892fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java @@ -124,7 +124,7 @@ public class ExportHelper { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle Bundle data = message.getData(); |