diff options
Diffstat (limited to 'OpenKeychain/src')
105 files changed, 3689 insertions, 1076 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index a00b6f01c..f68499fc8 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -424,6 +424,15 @@ android:value=".ui.ViewKeyActivity" /> </activity> <activity + android:name=".ui.MultiCertifyKeyActivity" + android:configChanges="orientation|screenSize|keyboardHidden|keyboard" + android:label="@string/title_certify_key" + android:parentActivityName=".ui.AddKeysActivity"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".ui.KeyListActivity" /> + </activity> + <activity android:name=".ui.ImportKeysActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_import_keys" @@ -615,6 +624,16 @@ android:value=".ui.KeyListActivity" /> </activity> <activity + android:name=".ui.AddKeysActivity" + android:configChanges="orientation|screenSize|keyboardHidden|keyboard" + android:label="@string/title_add_keys" + android:windowSoftInputMode="stateHidden" + android:parentActivityName=".ui.KeyListActivity"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".ui.KeyListActivity" /> + </activity> + <activity android:name=".ui.ConsolidateDialogActivity" android:theme="@android:style/Theme.NoDisplay" /> <activity @@ -634,14 +653,14 @@ android:allowTaskReparenting="true" /> <!--<activity--> - <!--android:name=".ui.NfcIntentActivity"--> - <!--android:launchMode="singleTop">--> - <!--<intent-filter>--> - <!--<action android:name="android.nfc.action.NDEF_DISCOVERED" />--> - - <!--<category android:name="android.intent.category.DEFAULT" />--> - <!--<data android:host="my.yubico.com" android:scheme="https"/>--> - <!--</intent-filter>--> + <!--android:name=".ui.NfcIntentActivity"--> + <!--android:launchMode="singleTop">--> + <!--<intent-filter>--> + <!--<action android:name="android.nfc.action.NDEF_DISCOVERED" />--> + + <!--<category android:name="android.intent.category.DEFAULT" />--> + <!--<data android:host="my.yubico.com" android:scheme="https"/>--> + <!--</intent-filter>--> <!--</activity>--> <activity diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index dafa3aeed..854a90713 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -73,7 +73,10 @@ public class ImportKeysListEntry implements Serializable, Parcelable { } dest.writeString(mFingerprintHex); dest.writeString(mKeyIdHex); - dest.writeInt(mBitStrength); + dest.writeInt(mBitStrength == null ? 0 : 1); + if (mBitStrength != null) { + dest.writeInt(mBitStrength); + } dest.writeString(mAlgorithm); dest.writeByte((byte) (mSecretKey ? 1 : 0)); dest.writeByte((byte) (mSelected ? 1 : 0)); @@ -94,7 +97,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { vr.mDate = source.readInt() != 0 ? new Date(source.readLong()) : null; vr.mFingerprintHex = source.readString(); vr.mKeyIdHex = source.readString(); - vr.mBitStrength = source.readInt(); + vr.mBitStrength = source.readInt() != 0 ? source.readInt() : null; vr.mAlgorithm = source.readString(); vr.mSecretKey = source.readByte() == 1; vr.mSelected = source.readByte() == 1; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index f43cbbeef..08b7316aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -25,6 +25,7 @@ import org.sufficientlysecure.keychain.util.IterableIterator; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Date; /** A generic wrapped PGPKeyRing object. * @@ -51,6 +52,10 @@ public abstract class CanonicalizedKeyRing extends KeyRing { return mVerified; } + public byte[] getFingerprint() { + return getRing().getPublicKey().getFingerprint(); + } + public String getPrimaryUserId() throws PgpGeneralException { return getPublicKey().getPrimaryUserId(); } @@ -67,11 +72,21 @@ public abstract class CanonicalizedKeyRing extends KeyRing { return getPublicKey().getUnorderedUserIds(); } - public boolean isRevoked() throws PgpGeneralException { + public boolean isRevoked() { // Is the master key revoked? return getRing().getPublicKey().isRevoked(); } + public boolean isExpired() { + // Is the master key expired? + Date creationDate = getRing().getPublicKey().getCreationTime(); + Date expiryDate = getRing().getPublicKey().getValidSeconds() > 0 + ? new Date(creationDate.getTime() + getRing().getPublicKey().getValidSeconds() * 1000) : null; + + Date now = new Date(); + return creationDate.after(now) || (expiryDate != null && expiryDate.before(now)); + } + public boolean canCertify() throws PgpGeneralException { return getRing().getPublicKey().isEncryptionKey(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index bec07ce21..595f37872 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -278,13 +278,12 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { * Certify the given pubkeyid with the given masterkeyid. * * @param publicKeyRing Keyring to add certification to. - * @param userIds User IDs to certify, must not be null or empty + * @param userIds User IDs to certify, or all if null * @return A keyring with added certifications */ public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing, List<String> userIds, byte[] nfcSignedHash, Date nfcCreationTimestamp) - throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException, - PGPException, SignatureException { + throws PGPException { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { throw new PrivateKeyNotUnlockedException(); } @@ -314,7 +313,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey(); // fetch public key ring, add the certification and return it - for (String userId : new IterableIterator<String>(userIds.iterator())) { + Iterable<String> it = userIds != null ? userIds + : new IterableIterator<String>(publicKey.getUserIDs()); + for (String userId : it) { PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey); publicKey = PGPPublicKey.addCertification(publicKey, userId, sig); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index 3d41c928b..bd7606194 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -103,9 +103,9 @@ public class OpenPgpSignatureResultBuilder { Log.d(Constants.TAG, "signingRing.getUnorderedUserIds(): " + signingRing.getUnorderedUserIds()); setUserIds(signingRing.getUnorderedUserIds()); - // from KEY - setKeyExpired(signingKey.isExpired()); - setKeyRevoked(signingKey.isRevoked()); + // either master key is expired/revoked or this specific subkey is expired/revoked + setKeyExpired(signingRing.isExpired() || signingKey.isExpired()); + setKeyRevoked(signingRing.isRevoked() || signingKey.isRevoked()); } public OpenPgpSignatureResult build() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java new file mode 100644 index 000000000..b0c801a93 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java @@ -0,0 +1,149 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.openpgp.PGPException; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +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.results.CertifyResult; +import org.sufficientlysecure.keychain.service.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.service.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.service.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +public class PgpCertifyOperation { + + private AtomicBoolean mCancelled; + + private ProviderHelper mProviderHelper; + + public PgpCertifyOperation(ProviderHelper providerHelper, AtomicBoolean cancelled) { + mProviderHelper = providerHelper; + + mCancelled = cancelled; + } + + private boolean checkCancelled() { + return mCancelled != null && mCancelled.get(); + } + + public CertifyResult certify(CertifyActionsParcel parcel, String passphrase) { + + OperationLog log = new OperationLog(); + log.add(LogType.MSG_CRT, 0); + + // Retrieve and unlock secret key + CanonicalizedSecretKey certificationKey; + try { + log.add(LogType.MSG_CRT_MASTER_FETCH, 1); + CanonicalizedSecretKeyRing secretKeyRing = + mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId); + log.add(LogType.MSG_CRT_UNLOCK, 1); + certificationKey = secretKeyRing.getSecretKey(); + if (!certificationKey.unlock(passphrase)) { + log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2); + return new CertifyResult(CertifyResult.RESULT_ERROR, log); + } + } catch (PgpGeneralException e) { + log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2); + return new CertifyResult(CertifyResult.RESULT_ERROR, log); + } catch (NotFoundException e) { + log.add(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND, 2); + return new CertifyResult(CertifyResult.RESULT_ERROR, log); + } + + ArrayList<UncachedKeyRing> certifiedKeys = new ArrayList<UncachedKeyRing>(); + + log.add(LogType.MSG_CRT_CERTIFYING, 1); + + int certifyOk = 0, certifyError = 0; + + // Work through all requested certifications + for (CertifyAction action : parcel.mCertifyActions) { + + // Check if we were cancelled + if (checkCancelled()) { + log.add(LogType.MSG_OPERATION_CANCELLED, 0); + return new CertifyResult(CertifyResult.RESULT_CANCELLED, log); + } + + try { + + if (action.mUserIds == null) { + log.add(LogType.MSG_CRT_CERTIFY_ALL, 2, + KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId)); + } else { + log.add(LogType.MSG_CRT_CERTIFY_SOME, 2, action.mUserIds.size(), + KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId)); + } + + CanonicalizedPublicKeyRing publicRing = + mProviderHelper.getCanonicalizedPublicKeyRing(action.mMasterKeyId); + + UncachedKeyRing certifiedKey = certificationKey.certifyUserIds(publicRing, action.mUserIds, null, null); + certifiedKeys.add(certifiedKey); + + } catch (NotFoundException e) { + certifyError += 1; + log.add(LogType.MSG_CRT_WARN_NOT_FOUND, 3); + } catch (PGPException e) { + certifyError += 1; + log.add(LogType.MSG_CRT_WARN_CERT_FAILED, 3); + Log.e(Constants.TAG, "Encountered PGPException during certification", e); + } + + } + + log.add(LogType.MSG_CRT_SAVING, 1); + + // Check if we were cancelled + if (checkCancelled()) { + log.add(LogType.MSG_OPERATION_CANCELLED, 0); + return new CertifyResult(CertifyResult.RESULT_CANCELLED, log); + } + + // Write all certified keys into the database + for (UncachedKeyRing certifiedKey : certifiedKeys) { + + // Check if we were cancelled + if (checkCancelled()) { + log.add(LogType.MSG_OPERATION_CANCELLED, 0); + return new CertifyResult(CertifyResult.RESULT_CANCELLED, log, certifyOk, certifyError); + } + + log.add(LogType.MSG_CRT_SAVE, 2, + KeyFormattingUtils.convertKeyIdToHex(certifiedKey.getMasterKeyId())); + // store the signed key in our local cache + mProviderHelper.clearLog(); + SaveKeyringResult result = mProviderHelper.savePublicKeyRing(certifiedKey); + + if (result.success()) { + certifyOk += 1; + } else { + log.add(LogType.MSG_CRT_WARN_SAVE_FAILED, 3); + } + + log.add(result, 2); + + // TODO do something with import results + + } + + if (certifyOk == 0) { + log.add(LogType.MSG_CRT_ERROR_NOTHING, 0); + return new CertifyResult(CertifyResult.RESULT_ERROR, log, certifyOk, certifyError); + } + + log.add(LogType.MSG_CRT_SUCCESS, 0); + return new CertifyResult(CertifyResult.RESULT_OK, log, certifyOk, certifyError); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index dd0549adc..9b21b49ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -38,7 +38,6 @@ import org.sufficientlysecure.keychain.service.results.OperationResult.Operation import org.sufficientlysecure.keychain.service.results.ImportKeyResult; import org.sufficientlysecure.keychain.service.results.SaveKeyringResult; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressScaler; @@ -118,13 +117,17 @@ public class PgpImportExport { public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num) { updateProgress(R.string.progress_importing, 0, 100); + OperationLog log = new OperationLog(); + // If there aren't even any keys, do nothing here. if (entries == null || !entries.hasNext()) { return new ImportKeyResult( - ImportKeyResult.RESULT_FAIL_NOTHING, mProviderHelper.getLog(), 0, 0, 0, 0); + ImportKeyResult.RESULT_FAIL_NOTHING, log, 0, 0, 0, 0, + new long[]{}); } int newKeys = 0, oldKeys = 0, badKeys = 0, secret = 0; + ArrayList<Long> importedMasterKeyIds = new ArrayList<Long>(); int position = 0; double progSteps = 100.0 / num; @@ -155,6 +158,7 @@ public class PgpImportExport { } SaveKeyringResult result; + mProviderHelper.clearLog(); if (key.isSecret()) { result = mProviderHelper.saveSecretKeyRing(key, new ProgressScaler(mProgressable, (int)(position*progSteps), (int)((position+1)*progSteps), 100)); @@ -166,13 +170,17 @@ public class PgpImportExport { badKeys += 1; } else if (result.updated()) { oldKeys += 1; + importedMasterKeyIds.add(key.getMasterKeyId()); } else { newKeys += 1; if (key.isSecret()) { secret += 1; } + importedMasterKeyIds.add(key.getMasterKeyId()); } + log.add(result, 1); + } catch (IOException e) { Log.e(Constants.TAG, "Encountered bad key on import!", e); ++badKeys; @@ -184,7 +192,6 @@ public class PgpImportExport { position++; } - OperationLog log = mProviderHelper.getLog(); int resultType = 0; // special return case: no new keys at all if (badKeys == 0 && newKeys == 0 && oldKeys == 0) { @@ -211,8 +218,14 @@ public class PgpImportExport { resultType |= ImportKeyResult.RESULT_CANCELLED; } - return new ImportKeyResult(resultType, log, newKeys, oldKeys, badKeys, secret); + // convert to long array + long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()]; + for (int i = 0; i < importedMasterKeyIds.size(); ++i) { + importedMasterKeyIdsArray[i] = importedMasterKeyIds.get(i); + } + return new ImportKeyResult(resultType, log, newKeys, oldKeys, badKeys, secret, + importedMasterKeyIdsArray); } public Bundle exportKeyRings(ArrayList<Long> publicKeyRingMasterIds, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index c4070e39a..43256fe28 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -557,7 +557,7 @@ public class PgpKeyOperation { // keep track if we actually changed one boolean ok = false; - log.add(LogType.MSG_MF_UID_PRIMARY, indent); + log.add(LogType.MSG_MF_UID_PRIMARY, indent, saveParcel.mChangePrimaryUserId); indent += 1; // we work on the modifiedPublicKey here, to respect new or newly revoked uids diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index 7c640efb8..17d35dc1f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -366,6 +366,11 @@ public class UncachedKeyRing { for (byte[] rawUserId : new IterableIterator<byte[]>(masterKey.getRawUserIDs())) { String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); + // warn if user id was made with bad encoding + if (!Utf8Util.isValidUTF8(rawUserId)) { + log.add(LogType.MSG_KC_UID_WARN_ENCODING, indent); + } + // check for duplicate user ids if (processedUserIds.contains(userId)) { log.add(LogType.MSG_KC_UID_DUP, @@ -437,10 +442,6 @@ public class UncachedKeyRing { badCerts += 1; continue; } - // warn user if the signature was made with bad encoding - if (!Utf8Util.isValidUTF8(rawUserId)) { - log.add(LogType.MSG_KC_UID_WARN_ENCODING, indent); - } } catch (PgpGeneralException e) { log.add(LogType.MSG_KC_UID_BAD_ERR, indent, userId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java index ad3ebae5f..5a3770f2d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -125,9 +125,9 @@ public class CachedPublicKeyRing extends KeyRing { public boolean canCertify() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, - KeychainContract.KeyRings.CAN_CERTIFY, - ProviderHelper.FIELD_TYPE_INTEGER); - return (Long) data > 0; + KeychainContract.KeyRings.HAS_CERTIFY, + ProviderHelper.FIELD_TYPE_NULL); + return !((Boolean) data); } catch(ProviderHelper.NotFoundException e) { throw new PgpGeneralException(e); } 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 33f51cbf9..6127002bb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -89,7 +89,6 @@ public class KeychainContract { .parse("content://" + CONTENT_AUTHORITY); public static final String BASE_KEY_RINGS = "key_rings"; - public static final String BASE_DATA = "data"; public static final String PATH_UNIFIED = "unified"; @@ -243,6 +242,10 @@ public class KeychainContract { public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.user_ids"; + public static Uri buildUserIdsUri() { + return CONTENT_URI.buildUpon().appendPath(PATH_USER_IDS).build(); + } + public static Uri buildUserIdsUri(long masterKeyId) { return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_USER_IDS).build(); } 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 80f4610a4..d40287690 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -51,6 +51,7 @@ public class KeychainProvider extends ContentProvider { private static final int KEY_RINGS_UNIFIED = 101; private static final int KEY_RINGS_PUBLIC = 102; private static final int KEY_RINGS_SECRET = 103; + private static final int KEY_RINGS_USER_IDS = 104; private static final int KEY_RING_UNIFIED = 200; private static final int KEY_RING_KEYS = 201; @@ -85,17 +86,22 @@ public class KeychainProvider extends ContentProvider { * <pre> * key_rings/unified * key_rings/public + * key_rings/secret + * key_rings/user_ids * </pre> */ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS - + "/" + KeychainContract.PATH_UNIFIED, + + "/" + KeychainContract.PATH_UNIFIED, KEY_RINGS_UNIFIED); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS - + "/" + KeychainContract.PATH_PUBLIC, + + "/" + KeychainContract.PATH_PUBLIC, KEY_RINGS_PUBLIC); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS - + "/" + KeychainContract.PATH_SECRET, + + "/" + KeychainContract.PATH_SECRET, KEY_RINGS_SECRET); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + + "/" + KeychainContract.PATH_USER_IDS, + KEY_RINGS_USER_IDS); /** * find by criteria other than master key id @@ -450,6 +456,7 @@ public class KeychainProvider extends ContentProvider { break; } + case KEY_RINGS_USER_IDS: case KEY_RING_USER_IDS: { HashMap<String, String> projectionMap = new HashMap<String, String>(); projectionMap.put(UserIds._ID, Tables.USER_IDS + ".oid AS _id"); @@ -470,13 +477,18 @@ public class KeychainProvider extends ContentProvider { + Tables.CERTS + "." + Certs.RANK + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0" + ")"); - groupBy = Tables.USER_IDS + "." + UserIds.RANK; + groupBy = Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + + ", " + Tables.USER_IDS + "." + UserIds.RANK; - qb.appendWhere(Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); + // If we are searching for a particular keyring's ids, add where + if (match == KEY_RING_USER_IDS) { + qb.appendWhere(Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); + } if (TextUtils.isEmpty(sortOrder)) { - sortOrder = Tables.USER_IDS + "." + UserIds.RANK + " ASC"; + sortOrder = Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " ASC" + + "," + Tables.USER_IDS + "." + UserIds.RANK + " ASC"; } break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 6b96a1e6e..273f9c75f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -133,6 +133,12 @@ public class ProviderHelper { } } + public void clearLog() { + if (mLog != null) { + mLog.clear(); + } + } + // If we ever switch to api level 11, we can ditch this whole mess! public static final int FIELD_TYPE_NULL = 1; // this is called integer to stay coherent with the constants in Cursor (api level 11) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java new file mode 100644 index 000000000..dd9c0d769 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 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.service; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * This class is a a transferable representation for a number of keyrings to + * be certified. + */ +public class CertifyActionsParcel implements Parcelable { + + // the master key id to certify with + final public long mMasterKeyId; + public CertifyLevel mLevel; + + public ArrayList<CertifyAction> mCertifyActions = new ArrayList<CertifyAction>(); + + public CertifyActionsParcel(long masterKeyId) { + mMasterKeyId = masterKeyId; + mLevel = CertifyLevel.DEFAULT; + } + + public CertifyActionsParcel(Parcel source) { + mMasterKeyId = source.readLong(); + // just like parcelables, this is meant for ad-hoc IPC only and is NOT portable! + mLevel = CertifyLevel.values()[source.readInt()]; + + mCertifyActions = (ArrayList<CertifyAction>) source.readSerializable(); + } + + public void add(CertifyAction action) { + mCertifyActions.add(action); + } + + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeLong(mMasterKeyId); + destination.writeInt(mLevel.ordinal()); + + destination.writeSerializable(mCertifyActions); + } + + public static final Creator<CertifyActionsParcel> CREATOR = new Creator<CertifyActionsParcel>() { + public CertifyActionsParcel createFromParcel(final Parcel source) { + return new CertifyActionsParcel(source); + } + + public CertifyActionsParcel[] newArray(final int size) { + return new CertifyActionsParcel[size]; + } + }; + + // TODO make this parcelable + public static class CertifyAction implements Serializable { + final public long mMasterKeyId; + + final public ArrayList<String> mUserIds; + + public CertifyAction(long masterKeyId) { + this(masterKeyId, null); + } + + public CertifyAction(long masterKeyId, ArrayList<String> userIds) { + mMasterKeyId = masterKeyId; + mUserIds = userIds; + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + String out = "mMasterKeyId: " + mMasterKeyId + "\n"; + out += "mLevel: " + mLevel + "\n"; + out += "mCertifyActions: " + mCertifyActions + "\n"; + + return out; + } + + // All supported algorithms + public enum CertifyLevel { + DEFAULT, NONE, CASUAL, POSITIVE + } + +} 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 2101705bc..b9c42db3f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -29,7 +29,9 @@ import android.os.RemoteException; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; +import org.sufficientlysecure.keychain.service.results.CertifyResult; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.Preferences; @@ -39,7 +41,6 @@ import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; @@ -80,7 +81,6 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -183,10 +183,8 @@ public class KeychainIntentService extends IntentService implements Progressable public static final String DOWNLOAD_KEY_SERVER = "query_key_server"; public static final String DOWNLOAD_KEY_LIST = "query_key_id"; - // sign key - public static final String CERTIFY_KEY_MASTER_KEY_ID = "sign_key_master_key_id"; - public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id"; - public static final String CERTIFY_KEY_UIDS = "sign_key_uids"; + // certify key + public static final String CERTIFY_PARCEL = "certify_parcel"; // consolidate public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery"; @@ -253,90 +251,75 @@ public class KeychainIntentService extends IntentService implements Progressable String action = intent.getAction(); // executeServiceMethod action from extra bundle - if (ACTION_SIGN_ENCRYPT.equals(action)) { - try { - /* Input */ - int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); - Bundle resultData = new Bundle(); - - long sigMasterKeyId = data.getLong(ENCRYPT_SIGNATURE_MASTER_ID); - String sigKeyPassphrase = data.getString(ENCRYPT_SIGNATURE_KEY_PASSPHRASE); + if (ACTION_CERTIFY_KEYRING.equals(action)) { - byte[] nfcHash = data.getByteArray(ENCRYPT_SIGNATURE_NFC_HASH); - Date nfcTimestamp = (Date) data.getSerializable(ENCRYPT_SIGNATURE_NFC_TIMESTAMP); + try { - String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE); + /* Input */ + CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL); - boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR); - long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS); - int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID); - int urisCount = data.containsKey(ENCRYPT_INPUT_URIS) ? data.getParcelableArrayList(ENCRYPT_INPUT_URIS).size() : 1; - for (int i = 0; i < urisCount; i++) { - data.putInt(SELECTED_URI, i); - InputData inputData = createEncryptInputData(data); - OutputStream outStream = createCryptOutputStream(data); - String originalFilename = getOriginalFilename(data); + /* Operation */ + String passphrase = PassphraseCacheService.getCachedPassphrase(this, + // certification is always with the master key id, so use that one + parcel.mMasterKeyId, parcel.mMasterKeyId); + if (passphrase == null) { + throw new PgpGeneralException("Unable to obtain passphrase"); + } - /* Operation */ - PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( - new ProviderHelper(this), this, inputData, outStream - ); - builder.setProgressable(this) - .setEnableAsciiArmorOutput(useAsciiArmor) - .setVersionHeader(PgpHelper.getVersionForHeader(this)) - .setCompressionId(compressionId) - .setSymmetricEncryptionAlgorithm( - Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) - .setEncryptionMasterKeyIds(encryptionKeyIds) - .setSymmetricPassphrase(symmetricPassphrase) - .setOriginalFilename(originalFilename); + ProviderHelper providerHelper = new ProviderHelper(this); + PgpCertifyOperation op = new PgpCertifyOperation(providerHelper, mActionCanceled); + CertifyResult result = op.certify(parcel, passphrase); - try { + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - // Find the appropriate subkey to sign with - CachedPublicKeyRing signingRing = - new ProviderHelper(this).getCachedPublicKeyRing(sigMasterKeyId); - long sigSubKeyId = signingRing.getSecretSignId(); + } catch (Exception e) { + sendErrorToHandler(e); + } - // Set signature settings - builder.setSignatureMasterKeyId(sigMasterKeyId) - .setSignatureSubKeyId(sigSubKeyId) - .setSignaturePassphrase(sigKeyPassphrase) - .setSignatureHashAlgorithm( - Preferences.getPreferences(this).getDefaultHashAlgorithm()) - .setAdditionalEncryptId(sigMasterKeyId); - if (nfcHash != null && nfcTimestamp != null) { - builder.setNfcState(nfcHash, nfcTimestamp); - } + } else if (ACTION_CONSOLIDATE.equals(action)) { - } catch (PgpGeneralException e) { - // encrypt-only - // TODO Just silently drop the requested signature? Shouldn't we throw here? - } + ConsolidateResult result; + if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) { + result = new ProviderHelper(this).consolidateDatabaseStep2(this); + } else { + result = new ProviderHelper(this).consolidateDatabaseStep1(this); + } + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - // this assumes that the bytes are cleartext (valid for current implementation!) - if (source == IO_BYTES) { - builder.setCleartextInput(true); - } + } else if (ACTION_DECRYPT_METADATA.equals(action)) { - SignEncryptResult result = builder.build().execute(); - resultData.putParcelable(SignEncryptResult.EXTRA_RESULT, result); + try { + /* Input */ + String passphrase = data.getString(DECRYPT_PASSPHRASE); + byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY); - outStream.close(); + InputData inputData = createDecryptInputData(data); - /* Output */ + /* Operation */ - finalizeEncryptOutputStream(data, resultData, outStream); + Bundle resultData = new Bundle(); - } + // verifyText and decrypt returning additional resultData values for the + // verification of signatures + PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( + new ProviderHelper(this), + this, inputData, null + ); + builder.setProgressable(this) + .setAllowSymmetricDecryption(true) + .setPassphrase(passphrase) + .setDecryptMetadataOnly(true) + .setNfcState(nfcDecryptedSessionKey); - Log.logDebugBundle(resultData, "resultData"); + DecryptVerifyResult decryptVerifyResult = builder.build().execute(); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, decryptVerifyResult); } catch (Exception e) { sendErrorToHandler(e); } + } else if (ACTION_DECRYPT_VERIFY.equals(action)) { + try { /* Input */ String passphrase = data.getString(DECRYPT_PASSPHRASE); @@ -376,42 +359,128 @@ public class KeychainIntentService extends IntentService implements Progressable } catch (Exception e) { sendErrorToHandler(e); } - } else if (ACTION_DECRYPT_METADATA.equals(action)) { + + } else if (ACTION_DELETE.equals(action)) { + try { - /* Input */ - String passphrase = data.getString(DECRYPT_PASSPHRASE); - byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY); - InputData inputData = createDecryptInputData(data); + long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST); + boolean isSecret = data.getBoolean(DELETE_IS_SECRET); - /* Operation */ + if (masterKeyIds.length == 0) { + throw new PgpGeneralException("List of keys to delete is empty"); + } - Bundle resultData = new Bundle(); + if (isSecret && masterKeyIds.length > 1) { + throw new PgpGeneralException("Secret keys can only be deleted individually!"); + } - // verifyText and decrypt returning additional resultData values for the - // verification of signatures - PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( - new ProviderHelper(this), - this, inputData, null - ); - builder.setProgressable(this) - .setAllowSymmetricDecryption(true) - .setPassphrase(passphrase) - .setDecryptMetadataOnly(true) - .setNfcState(nfcDecryptedSessionKey); + boolean success = false; + for (long masterKeyId : masterKeyIds) { + int count = getContentResolver().delete( + KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null + ); + success |= count > 0; + } - DecryptVerifyResult decryptVerifyResult = builder.build().execute(); + if (isSecret && success) { + new ProviderHelper(this).consolidateDatabaseStep1(this); + } - resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult); + if (success) { + // make sure new data is synced into contacts + ContactSyncAdapterService.requestSync(); - /* Output */ - Log.logDebugBundle(resultData, "resultData"); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); + } + } catch (Exception e) { + sendErrorToHandler(e); + } - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); + } else if (ACTION_DELETE_FILE_SECURELY.equals(action)) { + + try { + /* Input */ + String deleteFile = data.getString(DELETE_FILE); + + /* Operation */ + try { + PgpHelper.deleteFileSecurely(this, this, new File(deleteFile)); + } catch (FileNotFoundException e) { + throw new PgpGeneralException( + getString(R.string.error_file_not_found, deleteFile)); + } catch (IOException e) { + throw new PgpGeneralException(getString(R.string.error_file_delete_failed, + deleteFile)); + } + + /* Output */ + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); } catch (Exception e) { sendErrorToHandler(e); } + + } else if (ACTION_DOWNLOAD_AND_IMPORT_KEYS.equals(action) || ACTION_IMPORT_KEYBASE_KEYS.equals(action)) { + + ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST); + + // this downloads the keys and places them into the ImportKeysListEntry entries + String keyServer = data.getString(DOWNLOAD_KEY_SERVER); + + ArrayList<ParcelableKeyRing> keyRings = new ArrayList<ParcelableKeyRing>(entries.size()); + for (ImportKeysListEntry entry : entries) { + try { + Keyserver server; + ArrayList<String> origins = entry.getOrigins(); + if (origins == null) { + origins = new ArrayList<String>(); + } + if (origins.isEmpty()) { + origins.add(keyServer); + } + for (String origin : origins) { + if (KeybaseKeyserver.ORIGIN.equals(origin)) { + server = new KeybaseKeyserver(); + } else { + server = new HkpKeyserver(origin); + } + Log.d(Constants.TAG, "IMPORTING " + entry.getKeyIdHex() + " FROM: " + server); + + // if available use complete fingerprint for get request + byte[] downloadedKeyBytes; + if (KeybaseKeyserver.ORIGIN.equals(origin)) { + downloadedKeyBytes = server.get(entry.getExtraData()).getBytes(); + } else if (entry.getFingerprintHex() != null) { + downloadedKeyBytes = server.get("0x" + entry.getFingerprintHex()).getBytes(); + } else { + downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes(); + } + + // save key bytes in entry object for doing the + // actual import afterwards + keyRings.add(new ParcelableKeyRing(downloadedKeyBytes, entry.getFingerprintHex())); + } + } catch (Keyserver.QueryFailedException e) { + sendErrorToHandler(e); + } + } + + Intent importIntent = new Intent(this, KeychainIntentService.class); + importIntent.setAction(ACTION_IMPORT_KEYRING); + + Bundle importData = new Bundle(); + // This is not going through binder, nothing to fear of + importData.putParcelableArrayList(IMPORT_KEY_LIST, keyRings); + importIntent.putExtra(EXTRA_DATA, importData); + importIntent.putExtra(EXTRA_MESSENGER, mMessenger); + + // now import it with this service + onHandleIntent(importIntent); + + // result is handled in ACTION_IMPORT_KEYRING + } else if (ACTION_EDIT_KEYRING.equals(action)) { + try { /* Input */ SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL); @@ -438,11 +507,7 @@ public class KeychainIntentService extends IntentService implements Progressable // If the edit operation didn't succeed, exit here if (!modifyResult.success()) { // always return SaveKeyringResult, so create one out of the EditKeyResult - SaveKeyringResult saveResult = new SaveKeyringResult( - SaveKeyringResult.RESULT_ERROR, - modifyResult.getLog(), - null); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, modifyResult); return; } @@ -456,9 +521,9 @@ public class KeychainIntentService extends IntentService implements Progressable log.add(LogType.MSG_OPERATION_CANCELLED, 0); } // If so, just stop without saving - SaveKeyringResult saveResult = new SaveKeyringResult( - SaveKeyringResult.RESULT_CANCELLED, log, null); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult); + modifyResult = new EditKeyResult( + EditKeyResult.RESULT_CANCELLED, log, null); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, modifyResult); return; } @@ -489,66 +554,8 @@ public class KeychainIntentService extends IntentService implements Progressable sendErrorToHandler(e); } - } else if (ACTION_DELETE_FILE_SECURELY.equals(action)) { - try { - /* Input */ - String deleteFile = data.getString(DELETE_FILE); - - /* Operation */ - try { - PgpHelper.deleteFileSecurely(this, this, new File(deleteFile)); - } catch (FileNotFoundException e) { - throw new PgpGeneralException( - getString(R.string.error_file_not_found, deleteFile)); - } catch (IOException e) { - throw new PgpGeneralException(getString(R.string.error_file_delete_failed, - deleteFile)); - } - - /* Output */ - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); - } catch (Exception e) { - sendErrorToHandler(e); - } - } else if (ACTION_IMPORT_KEYRING.equals(action)) { - try { - - Iterator<ParcelableKeyRing> entries; - int numEntries; - if (data.containsKey(IMPORT_KEY_LIST)) { - // get entries from intent - ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST); - entries = list.iterator(); - numEntries = list.size(); - } else { - // get entries from cached file - ParcelableFileCache<ParcelableKeyRing> cache = - new ParcelableFileCache<ParcelableKeyRing>(this, "key_import.pcl"); - IteratorWithSize<ParcelableKeyRing> it = cache.readCache(); - entries = it; - numEntries = it.getSize(); - } - - ProviderHelper providerHelper = new ProviderHelper(this); - PgpImportExport pgpImportExport = new PgpImportExport( - this, providerHelper, this, mActionCanceled); - ImportKeyResult result = pgpImportExport.importKeyRings(entries, numEntries); - - // we do this even on failure or cancellation! - if (result.mSecret > 0) { - // cannot cancel from here on out! - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_PREVENT_CANCEL); - providerHelper.consolidateDatabaseStep1(this); - } - - // make sure new data is synced into contacts - ContactSyncAdapterService.requestSync(); - - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - } catch (Exception e) { - sendErrorToHandler(e); - } } else if (ACTION_EXPORT_KEYRING.equals(action)) { + try { boolean exportSecret = data.getBoolean(EXPORT_SECRET, false); @@ -614,166 +621,157 @@ public class KeychainIntentService extends IntentService implements Progressable } catch (Exception e) { sendErrorToHandler(e); } - } else if (ACTION_UPLOAD_KEYRING.equals(action)) { - try { - /* Input */ - String keyServer = data.getString(UPLOAD_KEY_SERVER); - // and dataUri! + } else if (ACTION_IMPORT_KEYRING.equals(action)) { - /* Operation */ - HkpKeyserver server = new HkpKeyserver(keyServer); + try { + + Iterator<ParcelableKeyRing> entries; + int numEntries; + if (data.containsKey(IMPORT_KEY_LIST)) { + // get entries from intent + ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST); + entries = list.iterator(); + numEntries = list.size(); + } else { + // get entries from cached file + ParcelableFileCache<ParcelableKeyRing> cache = + new ParcelableFileCache<ParcelableKeyRing>(this, "key_import.pcl"); + IteratorWithSize<ParcelableKeyRing> it = cache.readCache(); + entries = it; + numEntries = it.getSize(); + } ProviderHelper providerHelper = new ProviderHelper(this); - CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri); - PgpImportExport pgpImportExport = new PgpImportExport(this, new ProviderHelper(this), this); + PgpImportExport pgpImportExport = new PgpImportExport( + this, providerHelper, this, mActionCanceled); + ImportKeyResult result = pgpImportExport.importKeyRings(entries, numEntries); - try { - pgpImportExport.uploadKeyRingToServer(server, keyring); - } catch (Keyserver.AddKeyException e) { - throw new PgpGeneralException("Unable to export key to selected server"); + // we do this even on failure or cancellation! + if (result.mSecret > 0) { + // cannot cancel from here on out! + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_PREVENT_CANCEL); + providerHelper.consolidateDatabaseStep1(this); } - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); + // make sure new data is synced into contacts + ContactSyncAdapterService.requestSync(); + + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); } catch (Exception e) { sendErrorToHandler(e); } - } else if (ACTION_DOWNLOAD_AND_IMPORT_KEYS.equals(action) || ACTION_IMPORT_KEYBASE_KEYS.equals(action)) { - ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST); - // this downloads the keys and places them into the ImportKeysListEntry entries - String keyServer = data.getString(DOWNLOAD_KEY_SERVER); + } else if (ACTION_SIGN_ENCRYPT.equals(action)) { + + try { + /* Input */ + int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); + Bundle resultData = new Bundle(); + + long sigMasterKeyId = data.getLong(ENCRYPT_SIGNATURE_MASTER_ID); + String sigKeyPassphrase = data.getString(ENCRYPT_SIGNATURE_KEY_PASSPHRASE); + + byte[] nfcHash = data.getByteArray(ENCRYPT_SIGNATURE_NFC_HASH); + Date nfcTimestamp = (Date) data.getSerializable(ENCRYPT_SIGNATURE_NFC_TIMESTAMP); + + String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE); + + boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR); + long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS); + int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID); + int urisCount = data.containsKey(ENCRYPT_INPUT_URIS) ? data.getParcelableArrayList(ENCRYPT_INPUT_URIS).size() : 1; + for (int i = 0; i < urisCount; i++) { + data.putInt(SELECTED_URI, i); + InputData inputData = createEncryptInputData(data); + OutputStream outStream = createCryptOutputStream(data); + String originalFilename = getOriginalFilename(data); + + /* Operation */ + PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( + new ProviderHelper(this), this, inputData, outStream + ); + builder.setProgressable(this) + .setEnableAsciiArmorOutput(useAsciiArmor) + .setVersionHeader(PgpHelper.getVersionForHeader(this)) + .setCompressionId(compressionId) + .setSymmetricEncryptionAlgorithm( + Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) + .setEncryptionMasterKeyIds(encryptionKeyIds) + .setSymmetricPassphrase(symmetricPassphrase) + .setOriginalFilename(originalFilename); - ArrayList<ParcelableKeyRing> keyRings = new ArrayList<ParcelableKeyRing>(entries.size()); - for (ImportKeysListEntry entry : entries) { try { - Keyserver server; - ArrayList<String> origins = entry.getOrigins(); - if (origins == null) { - origins = new ArrayList<String>(); - } - if (origins.isEmpty()) { - origins.add(keyServer); - } - for (String origin : origins) { - if (KeybaseKeyserver.ORIGIN.equals(origin)) { - server = new KeybaseKeyserver(); - } else { - server = new HkpKeyserver(origin); - } - Log.d(Constants.TAG, "IMPORTING " + entry.getKeyIdHex() + " FROM: " + server); - - // if available use complete fingerprint for get request - byte[] downloadedKeyBytes; - if (KeybaseKeyserver.ORIGIN.equals(origin)) { - downloadedKeyBytes = server.get(entry.getExtraData()).getBytes(); - } else if (entry.getFingerprintHex() != null) { - downloadedKeyBytes = server.get("0x" + entry.getFingerprintHex()).getBytes(); - } else { - downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes(); - } - - // save key bytes in entry object for doing the - // actual import afterwards - keyRings.add(new ParcelableKeyRing(downloadedKeyBytes, entry.getFingerprintHex())); + + // Find the appropriate subkey to sign with + CachedPublicKeyRing signingRing = + new ProviderHelper(this).getCachedPublicKeyRing(sigMasterKeyId); + long sigSubKeyId = signingRing.getSecretSignId(); + + // Set signature settings + builder.setSignatureMasterKeyId(sigMasterKeyId) + .setSignatureSubKeyId(sigSubKeyId) + .setSignaturePassphrase(sigKeyPassphrase) + .setSignatureHashAlgorithm( + Preferences.getPreferences(this).getDefaultHashAlgorithm()) + .setAdditionalEncryptId(sigMasterKeyId); + if (nfcHash != null && nfcTimestamp != null) { + builder.setNfcState(nfcHash, nfcTimestamp); } - } catch (Exception e) { - sendErrorToHandler(e); + + } catch (PgpGeneralException e) { + // encrypt-only + // TODO Just silently drop the requested signature? Shouldn't we throw here? } - } - Intent importIntent = new Intent(this, KeychainIntentService.class); - importIntent.setAction(ACTION_IMPORT_KEYRING); + // this assumes that the bytes are cleartext (valid for current implementation!) + if (source == IO_BYTES) { + builder.setCleartextInput(true); + } - Bundle importData = new Bundle(); - // This is not going through binder, nothing to fear of - importData.putParcelableArrayList(IMPORT_KEY_LIST, keyRings); - importIntent.putExtra(EXTRA_DATA, importData); - importIntent.putExtra(EXTRA_MESSENGER, mMessenger); + SignEncryptResult result = builder.build().execute(); + resultData.putParcelable(SignEncryptResult.EXTRA_RESULT, result); - // now import it with this service - onHandleIntent(importIntent); + outStream.close(); - // result is handled in ACTION_IMPORT_KEYRING - } else if (ACTION_CERTIFY_KEYRING.equals(action)) { - try { + /* Output */ - /* Input */ - long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID); - long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID); - ArrayList<String> userIds = data.getStringArrayList(CERTIFY_KEY_UIDS); + finalizeEncryptOutputStream(data, resultData, outStream); - /* Operation */ - String signaturePassphrase = PassphraseCacheService.getCachedPassphrase(this, - masterKeyId, masterKeyId); - if (signaturePassphrase == null) { - throw new PgpGeneralException("Unable to obtain passphrase"); } - ProviderHelper providerHelper = new ProviderHelper(this); - CanonicalizedPublicKeyRing publicRing = providerHelper.getCanonicalizedPublicKeyRing(pubKeyId); - CanonicalizedSecretKeyRing secretKeyRing = providerHelper.getCanonicalizedSecretKeyRing(masterKeyId); - CanonicalizedSecretKey certificationKey = secretKeyRing.getSecretKey(); - if (!certificationKey.unlock(signaturePassphrase)) { - throw new PgpGeneralException("Error extracting key (bad passphrase?)"); - } - // TODO: supply nfc stuff - UncachedKeyRing newRing = certificationKey.certifyUserIds(publicRing, userIds, null, null); - - // store the signed key in our local cache - providerHelper.savePublicKeyRing(newRing); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); + Log.logDebugBundle(resultData, "resultData"); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); } catch (Exception e) { sendErrorToHandler(e); } - } else if (ACTION_DELETE.equals(action)) { + } else if (ACTION_UPLOAD_KEYRING.equals(action)) { try { - long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST); - boolean isSecret = data.getBoolean(DELETE_IS_SECRET); - - if (masterKeyIds.length == 0) { - throw new PgpGeneralException("List of keys to delete is empty"); - } - - if (isSecret && masterKeyIds.length > 1) { - throw new PgpGeneralException("Secret keys can only be deleted individually!"); - } - - boolean success = false; - for (long masterKeyId : masterKeyIds) { - int count = getContentResolver().delete( - KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null - ); - success |= count > 0; - } + /* Input */ + String keyServer = data.getString(UPLOAD_KEY_SERVER); + // and dataUri! - if (isSecret && success) { - new ProviderHelper(this).consolidateDatabaseStep1(this); - } + /* Operation */ + HkpKeyserver server = new HkpKeyserver(keyServer); - if (success) { - // make sure new data is synced into contacts - ContactSyncAdapterService.requestSync(); + ProviderHelper providerHelper = new ProviderHelper(this); + CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri); + PgpImportExport pgpImportExport = new PgpImportExport(this, new ProviderHelper(this), this); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); + try { + pgpImportExport.uploadKeyRingToServer(server, keyring); + } catch (Keyserver.AddKeyException e) { + throw new PgpGeneralException("Unable to export key to selected server"); } + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); } catch (Exception e) { sendErrorToHandler(e); } - - } else if (ACTION_CONSOLIDATE.equals(action)) { - ConsolidateResult result; - if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) { - result = new ProviderHelper(this).consolidateDatabaseStep2(this); - } else { - result = new ProviderHelper(this).consolidateDatabaseStep1(this); - } - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/CertifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/CertifyResult.java new file mode 100644 index 000000000..49bc613bd --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/CertifyResult.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 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.service.results; + +import android.app.Activity; +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.View; + +import com.github.johnpersano.supertoasts.SuperCardToast; +import com.github.johnpersano.supertoasts.SuperToast; +import com.github.johnpersano.supertoasts.SuperToast.Duration; +import com.github.johnpersano.supertoasts.util.OnClickWrapper; +import com.github.johnpersano.supertoasts.util.Style; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.LogDisplayActivity; +import org.sufficientlysecure.keychain.ui.LogDisplayFragment; + +public class CertifyResult extends OperationResult { + + int mCertifyOk, mCertifyError; + + public CertifyResult(int result, OperationLog log) { + super(result, log); + } + + public CertifyResult(int result, OperationLog log, int certifyOk, int certifyError) { + this(result, log); + mCertifyOk = certifyOk; + mCertifyError = certifyError; + } + + /** Construct from a parcel - trivial because we have no extra data. */ + public CertifyResult(Parcel source) { + super(source); + mCertifyOk = source.readInt(); + mCertifyError = source.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mCertifyOk); + dest.writeInt(mCertifyError); + } + + public static Creator<CertifyResult> CREATOR = new Creator<CertifyResult>() { + public CertifyResult createFromParcel(final Parcel source) { + return new CertifyResult(source); + } + + public CertifyResult[] newArray(final int size) { + return new CertifyResult[size]; + } + }; + + public SuperCardToast createNotify(final Activity activity) { + + int resultType = getResult(); + + String str; + int duration, color; + + // Not an overall failure + if ((resultType & OperationResult.RESULT_ERROR) == 0) { + String withWarnings; + + duration = Duration.EXTRA_LONG; + color = Style.GREEN; + withWarnings = ""; + + // Any warnings? + if ((resultType & ImportKeyResult.RESULT_WARNINGS) > 0) { + duration = 0; + color = Style.ORANGE; + withWarnings += activity.getString(R.string.with_warnings); + } + if ((resultType & ImportKeyResult.RESULT_CANCELLED) > 0) { + duration = 0; + color = Style.ORANGE; + withWarnings += activity.getString(R.string.with_cancelled); + } + + // New and updated keys + str = activity.getResources().getQuantityString( + R.plurals.certify_keys_ok, mCertifyOk, mCertifyOk, withWarnings); + if (mCertifyError > 0) { + // definitely switch to warning-style message in this case! + duration = 0; + color = Style.RED; + str += " " + activity.getResources().getQuantityString( + R.plurals.certify_keys_with_errors, mCertifyError, mCertifyError); + } + + } else { + duration = 0; + color = Style.RED; + str = activity.getResources().getQuantityString(R.plurals.certify_error, + mCertifyError, mCertifyError); + } + + boolean button = getLog() != null && !getLog().isEmpty(); + SuperCardToast toast = new SuperCardToast(activity, + button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD, + Style.getStyle(color, SuperToast.Animations.POPUP)); + toast.setText(str); + toast.setDuration(duration); + toast.setIndeterminate(duration == 0); + toast.setSwipeToDismiss(true); + // If we have a log and it's non-empty, show a View Log button + if (button) { + toast.setButtonIcon(R.drawable.ic_action_view_as_list, + activity.getResources().getString(R.string.view_log)); + toast.setButtonTextColor(activity.getResources().getColor(R.color.black)); + toast.setTextColor(activity.getResources().getColor(R.color.black)); + toast.setOnClickWrapper(new OnClickWrapper("supercardtoast", + new SuperToast.OnClickListener() { + @Override + public void onClick(View view, Parcelable token) { + Intent intent = new Intent( + activity, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, CertifyResult.this); + activity.startActivity(intent); + } + } + )); + } + + return toast; + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/GetKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/GetKeyResult.java new file mode 100644 index 000000000..e76d1bd41 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/GetKeyResult.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 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.service.results; + +import android.os.Parcel; + +public class GetKeyResult extends OperationResult { + + public int mNonPgpPartsCount; + + public int getNonPgpPartsCount() { + return mNonPgpPartsCount; + } + + public void setNonPgpPartsCount(int nonPgpPartsCount) { + mNonPgpPartsCount = nonPgpPartsCount; + } + + public GetKeyResult(int result, OperationLog log) { + super(result, log); + } + + public static final int RESULT_ERROR_NO_VALID_KEYS = RESULT_ERROR + 8; + public static final int RESULT_ERROR_NO_PGP_PARTS = RESULT_ERROR + 16; + public static final int RESULT_ERROR_QUERY_TOO_SHORT = RESULT_ERROR + 32; + public static final int RESULT_ERROR_TOO_MANY_RESPONSES = RESULT_ERROR + 64; + public static final int RESULT_ERROR_TOO_SHORT_OR_TOO_MANY_RESPONSES = RESULT_ERROR + 128; + public static final int RESULT_ERROR_QUERY_FAILED = RESULT_ERROR + 256; + + public GetKeyResult(Parcel source) { + super(source); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } + + public static Creator<GetKeyResult> CREATOR = new Creator<GetKeyResult>() { + public GetKeyResult createFromParcel(final Parcel source) { + return new GetKeyResult(source); + } + + public GetKeyResult[] newArray(final int size) { + return new GetKeyResult[size]; + } + }; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/ImportKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/ImportKeyResult.java index 188162aec..b54dcc5d6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/ImportKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/ImportKeyResult.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.ui.LogDisplayFragment; public class ImportKeyResult extends OperationResult { public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret; + public final long[] mImportedMasterKeyIds; // At least one new key public static final int RESULT_OK_NEWKEYS = 8; @@ -69,21 +70,28 @@ public class ImportKeyResult extends OperationResult { return (mResult & RESULT_FAIL_NOTHING) == RESULT_FAIL_NOTHING; } + public long[] getImportedMasterKeyIds() { + return mImportedMasterKeyIds; + } + public ImportKeyResult(Parcel source) { super(source); mNewKeys = source.readInt(); mUpdatedKeys = source.readInt(); mBadKeys = source.readInt(); mSecret = source.readInt(); + mImportedMasterKeyIds = source.createLongArray(); } public ImportKeyResult(int result, OperationLog log, - int newKeys, int updatedKeys, int badKeys, int secret) { + int newKeys, int updatedKeys, int badKeys, int secret, + long[] importedMasterKeyIds) { super(result, log); mNewKeys = newKeys; mUpdatedKeys = updatedKeys; mBadKeys = badKeys; mSecret = secret; + mImportedMasterKeyIds = importedMasterKeyIds; } @Override @@ -93,6 +101,7 @@ public class ImportKeyResult extends OperationResult { dest.writeInt(mUpdatedKeys); dest.writeInt(mBadKeys); dest.writeInt(mSecret); + dest.writeLongArray(mImportedMasterKeyIds); } public static Creator<ImportKeyResult> CREATOR = new Creator<ImportKeyResult>() { @@ -124,12 +133,12 @@ public class ImportKeyResult extends OperationResult { if ((resultType & ImportKeyResult.RESULT_WARNINGS) > 0) { duration = 0; color = Style.ORANGE; - withWarnings += activity.getString(R.string.import_with_warnings); + withWarnings += activity.getString(R.string.with_warnings); } if ((resultType & ImportKeyResult.RESULT_CANCELLED) > 0) { duration = 0; color = Style.ORANGE; - withWarnings += activity.getString(R.string.import_with_cancelled); + withWarnings += activity.getString(R.string.with_cancelled); } // New and updated keys @@ -162,8 +171,8 @@ public class ImportKeyResult extends OperationResult { color = Style.RED; if (isFailNothing()) { str = activity.getString((resultType & ImportKeyResult.RESULT_CANCELLED) > 0 - ? R.string.import_error_nothing_cancelled - : R.string.import_error_nothing); + ? R.string.import_error_nothing_cancelled + : R.string.import_error_nothing); } else { str = activity.getResources().getQuantityString(R.plurals.import_error, mBadKeys); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/OperationResult.java index 29924ee5d..e3f2c1cc5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/results/OperationResult.java @@ -93,6 +93,11 @@ public abstract class OperationResult implements Parcelable { } public OperationLog getLog() { + // If there is only a single entry, and it's a compound one, return that log + if (mLog.isSingleCompound()) { + return ((SubLogEntryParcel) mLog.getFirst()).getSubResult().getLog(); + } + // Otherwse, return our regular log return mLog; } @@ -106,7 +111,7 @@ public abstract class OperationResult implements Parcelable { mType = type; mParameters = parameters; mIndent = indent; - Log.v(Constants.TAG, "log: " + this.toString()); + Log.v(Constants.TAG, "log: " + this); } public LogEntryParcel(Parcel source) { @@ -122,6 +127,7 @@ public abstract class OperationResult implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(0); dest.writeInt(mType.ordinal()); dest.writeSerializable(mParameters); dest.writeInt(mIndent); @@ -129,7 +135,12 @@ public abstract class OperationResult implements Parcelable { public static final Creator<LogEntryParcel> CREATOR = new Creator<LogEntryParcel>() { public LogEntryParcel createFromParcel(final Parcel source) { - return new LogEntryParcel(source); + // Actually create LogEntryParcel or SubLogEntryParcel depending on type indicator + if (source.readInt() == 0) { + return new LogEntryParcel(source); + } else { + return new SubLogEntryParcel(source); + } } public LogEntryParcel[] newArray(final int size) { @@ -139,7 +150,7 @@ public abstract class OperationResult implements Parcelable { @Override public String toString() { - return "LogEntryParcel{" + + return getClass().getSimpleName() + "{" + "mLevel=" + mType.mLevel + ", mType=" + mType + ", mParameters=" + Arrays.toString(mParameters) + @@ -148,6 +159,42 @@ public abstract class OperationResult implements Parcelable { } } + public static class SubLogEntryParcel extends LogEntryParcel { + + OperationResult mSubResult; + + public SubLogEntryParcel(OperationResult subResult, LogType type, int indent, Object... parameters) { + super(type, indent, parameters); + mSubResult = subResult; + + Log.v(Constants.TAG, "log: " + this); + } + + public SubLogEntryParcel(Parcel source) { + super(source); + mSubResult = source.readParcelable(SubLogEntryParcel.class.getClassLoader()); + } + + public OperationResult getSubResult() { + return mSubResult; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(1); + dest.writeInt(mType.ordinal()); + dest.writeSerializable(mParameters); + dest.writeInt(mIndent); + dest.writeParcelable(mSubResult, 0); + } + + } + public SuperCardToast createNotify(final Activity activity) { int color; @@ -517,13 +564,36 @@ public abstract class OperationResult implements Parcelable { MSG_SE_SIGCRYPTING (LogLevel.DEBUG, R.string.msg_se_sigcrypting), MSG_SE_SYMMETRIC (LogLevel.INFO, R.string.msg_se_symmetric), - MSG_CRT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_crt_upload_success), + MSG_CRT_CERTIFYING (LogLevel.DEBUG, R.string.msg_crt_certifying), + MSG_CRT_CERTIFY_ALL (LogLevel.DEBUG, R.string.msg_crt_certify_all), + MSG_CRT_CERTIFY_SOME (LogLevel.DEBUG, R.plurals.msg_crt_certify_some), + MSG_CRT_ERROR_MASTER_NOT_FOUND (LogLevel.ERROR, R.string.msg_crt_error_master_not_found), + MSG_CRT_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_crt_error_nothing), + MSG_CRT_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_crt_error_unlock), + MSG_CRT (LogLevel.START, R.string.msg_crt), + MSG_CRT_MASTER_FETCH (LogLevel.DEBUG, R.string.msg_crt_master_fetch), + MSG_CRT_SAVE (LogLevel.DEBUG, R.string.msg_crt_save), + MSG_CRT_SAVING (LogLevel.DEBUG, R.string.msg_crt_saving), MSG_CRT_SUCCESS (LogLevel.OK, R.string.msg_crt_success), + MSG_CRT_UNLOCK (LogLevel.DEBUG, R.string.msg_crt_unlock), + MSG_CRT_WARN_NOT_FOUND (LogLevel.WARN, R.string.msg_crt_warn_not_found), + MSG_CRT_WARN_CERT_FAILED (LogLevel.WARN, R.string.msg_crt_warn_cert_failed), + MSG_CRT_WARN_SAVE_FAILED (LogLevel.WARN, R.string.msg_crt_warn_save_failed), + + MSG_CRT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_crt_upload_success), MSG_ACC_SAVED (LogLevel.INFO, R.string.api_settings_save_msg), - MSG_NO_VALID_ENC (LogLevel.ERROR, R.string.error_invalid_data) + MSG_NO_VALID_ENC (LogLevel.ERROR, R.string.error_invalid_data), + // get key + MSG_GET_SUCCESS(LogLevel.OK, R.string.msg_download_success), + MSG_GET_NO_VALID_KEYS(LogLevel.ERROR, R.string.msg_download_no_valid_keys), + MSG_GET_NO_PGP_PARTS(LogLevel.ERROR, R.string.msg_download_no_pgp_parts), + MSG_GET_QUERY_TOO_SHORT(LogLevel.ERROR, R.string.msg_download_query_too_short), + MSG_GET_TOO_MANY_RESPONSES(LogLevel.ERROR, R.string.msg_download_too_many_responses), + MSG_GET_QUERY_TOO_SHORT_OR_TOO_MANY_RESPONSES(LogLevel.ERROR, R.string.msg_download_query_too_short_or_too_many_responses), + MSG_GET_QUERY_FAILED(LogLevel.ERROR, R.string.msg_download_query_failed) ; public final int mMsgId; @@ -574,6 +644,19 @@ public abstract class OperationResult implements Parcelable { mParcels.add(new OperationResult.LogEntryParcel(type, indent, (Object[]) null)); } + public void add(OperationResult subResult, int indent) { + OperationLog subLog = subResult.getLog(); + mParcels.add(new SubLogEntryParcel(subResult, subLog.getFirst().mType, indent, subLog.getFirst().mParameters)); + } + + boolean isSingleCompound() { + return mParcels.size() == 1 && getFirst() instanceof SubLogEntryParcel; + } + + public void clear() { + mParcels.clear(); + } + public boolean containsType(LogType type) { for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) { if (entry.mType == type) { @@ -604,6 +687,13 @@ public abstract class OperationResult implements Parcelable { return mParcels.isEmpty(); } + public LogEntryParcel getFirst() { + if (mParcels.isEmpty()) { + return null; + } + return mParcels.get(0); + } + public LogEntryParcel getLast() { if (mParcels.isEmpty()) { return null; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/AddKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/AddKeysActivity.java new file mode 100644 index 000000000..427fc9315 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/AddKeysActivity.java @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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.ui; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.PorterDuff; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.support.v4.util.LongSparseArray; +import android.support.v7.app.ActionBarActivity; +import android.view.View; +import android.widget.ImageView; + +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +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.results.GetKeyResult; +import org.sufficientlysecure.keychain.service.results.ImportKeyResult; +import org.sufficientlysecure.keychain.service.results.OperationResult; +import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper; +import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.widget.ExchangeKeySpinner; +import org.sufficientlysecure.keychain.ui.widget.KeySpinner; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableFileCache; +import org.sufficientlysecure.keychain.util.Preferences; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Locale; + +import edu.cmu.cylab.starslinger.exchange.ExchangeActivity; +import edu.cmu.cylab.starslinger.exchange.ExchangeConfig; + +public class AddKeysActivity extends ActionBarActivity implements + LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { + + ExchangeKeySpinner mSafeSlingerKeySpinner; + View mActionSafeSlinger; + ImageView mActionSafeSlingerIcon; + View mActionQrCode; + View mActionNfc; + + ProviderHelper mProviderHelper; + + long mExchangeMasterKeyId = Constants.key.none; + + byte[] mImportBytes; + + private static final int REQUEST_CODE_RESULT = 0; + private static final int REQUEST_CODE_RESULT_TO_LIST = 1; + private static final int REQUEST_CODE_SAFE_SLINGER = 2; + + private static final int LOADER_ID_BYTES = 0; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mProviderHelper = new ProviderHelper(this); + + setContentView(R.layout.add_keys_activity); + + mSafeSlingerKeySpinner = (ExchangeKeySpinner) findViewById(R.id.add_keys_safeslinger_key_spinner); + mActionSafeSlinger = findViewById(R.id.add_keys_safeslinger); + mActionSafeSlingerIcon = (ImageView) findViewById(R.id.add_keys_safeslinger_icon); + // make certify image gray, like action icons + mActionSafeSlingerIcon.setColorFilter(getResources().getColor(R.color.tertiary_text_light), + PorterDuff.Mode.SRC_IN); + mActionQrCode = findViewById(R.id.add_keys_qr_code); + mActionNfc = findViewById(R.id.add_keys_nfc); + + mSafeSlingerKeySpinner.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() { + @Override + public void onKeyChanged(long masterKeyId) { + mExchangeMasterKeyId = masterKeyId; + } + }); + + mActionSafeSlinger.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startExchange(); + } + }); + + mActionQrCode.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startQrCode(); + } + }); + + mActionNfc.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // show nfc help + Intent intent = new Intent(AddKeysActivity.this, HelpActivity.class); + intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, HelpActivity.TAB_NFC); + startActivityForResult(intent, REQUEST_CODE_RESULT); + } + }); + } + + private void startExchange() { + if (mExchangeMasterKeyId == 0) { + Notify.showNotify(this, getString(R.string.select_key_for_exchange), + Notify.Style.ERROR); + } else { + // retrieve public key blob and start SafeSlinger + Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(mExchangeMasterKeyId); + try { + byte[] keyBlob = (byte[]) mProviderHelper.getGenericData( + uri, KeychainContract.KeyRingData.KEY_RING_DATA, ProviderHelper.FIELD_TYPE_BLOB); + + Intent slingerIntent = new Intent(this, ExchangeActivity.class); + slingerIntent.putExtra(ExchangeConfig.extra.USER_DATA, keyBlob); + slingerIntent.putExtra(ExchangeConfig.extra.HOST_NAME, Constants.SAFESLINGER_SERVER); + startActivityForResult(slingerIntent, REQUEST_CODE_SAFE_SLINGER); + } catch (ProviderHelper.NotFoundException e) { + Log.e(Constants.TAG, "personal key not found", e); + } + } + } + + private void startQrCode() { + // scan using xzing's Barcode Scanner + new IntentIntegrator(this).initiateScan(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_RESULT: { + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(this).show(); + } + break; + } + case REQUEST_CODE_RESULT_TO_LIST: { + // give it down... + setResult(0, data); + finish(); + break; + } + case REQUEST_CODE_SAFE_SLINGER: { + switch (resultCode) { + case ExchangeActivity.RESULT_EXCHANGE_OK: + // import exchanged keys + mImportBytes = getSlingedKeys(data); + getSupportLoaderManager().restartLoader(LOADER_ID_BYTES, null, this); + break; + case ExchangeActivity.RESULT_EXCHANGE_CANCELED: + // do nothing + break; + } + break; + } + case IntentIntegratorSupportV4.REQUEST_CODE: { + IntentResult scanResult = IntentIntegratorSupportV4.parseActivityResult(requestCode, + resultCode, data); + if (scanResult != null && scanResult.getFormatName() != null) { + String scannedContent = scanResult.getContents(); + + Log.d(Constants.TAG, "scannedContent: " + scannedContent); + + // look if it's fingerprint only + if (scannedContent.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) { + importKeys(null, getFingerprintFromUri(Uri.parse(scanResult.getContents()))); + return; + } + + // is this a full key encoded as qr code? + if (scannedContent.startsWith("-----BEGIN PGP")) { + // TODO +// mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(scannedContent.getBytes(), null)); + return; + } + + // fail... + Notify.showNotify(this, R.string.import_qr_code_wrong, Notify.Style.ERROR); + } + + break; + } + } + super.onActivityResult(requestCode, resultCode, data); + } + + private String getFingerprintFromUri(Uri dataUri) { + String fingerprint = dataUri.toString().split(":")[1].toLowerCase(Locale.ENGLISH); + Log.d(Constants.TAG, "fingerprint: " + fingerprint); + return fingerprint; + } + + private static byte[] getSlingedKeys(Intent data) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + Bundle extras = data.getExtras(); + if (extras != null) { + byte[] d; + int i = 0; + do { + d = extras.getByteArray(ExchangeConfig.extra.MEMBER_DATA + i); + if (d != null) { + try { + out.write(d); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException", e); + } + i++; + } + } while (d != null); + } + + return out.toByteArray(); + } + + @Override + public Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_ID_BYTES: { + InputData inputData = new InputData(new ByteArrayInputStream(mImportBytes), mImportBytes.length); + return new ImportKeysListLoader(this, inputData); + } + + default: + return null; + } + } + + @Override + public void onLoadFinished(Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader, + AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) { + Log.d(Constants.TAG, "data: " + data.getResult()); + + GetKeyResult getKeyResult = (GetKeyResult) data.getOperationResult(); + + LongSparseArray<ParcelableKeyRing> cachedKeyData = null; + + // TODO: Use parcels!!!!!!!!!!!!!!! + switch (loader.getId()) { + case LOADER_ID_BYTES: + + if (getKeyResult.success()) { + // No error + cachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings(); + } else { + getKeyResult.createNotify(this).show(); + } + +// if (error == null) { +// // No error +// cachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings(); +// Log.d(Constants.TAG, "no error!:" + cachedKeyData); +// +// } else if (error instanceof ImportKeysListLoader.NoValidKeysException) { +// Notify.showNotify(this, R.string.error_import_no_valid_keys, Notify.Style.ERROR); +// } else if (error instanceof ImportKeysListLoader.NonPgpPartException) { +// Notify.showNotify(this, +// ((ImportKeysListLoader.NonPgpPartException) error).getCount() + " " + getResources(). +// getQuantityString(R.plurals.error_import_non_pgp_part, +// ((ImportKeysListLoader.NonPgpPartException) error).getCount()), +// Notify.Style.OK +// ); +// } else { +// Notify.showNotify(this, R.string.error_generic_report_bug, Notify.Style.ERROR); +// } + break; + + + default: + break; + } + + importKeys(cachedKeyData, null); + } + + @Override + public void onLoaderReset(Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader) { + switch (loader.getId()) { + case LOADER_ID_BYTES: + // Clear the data in the adapter. +// mAdapter.clear(); + break; + default: + break; + } + } + + public ParcelableFileCache.IteratorWithSize<ParcelableKeyRing> + getSelectedData(final LongSparseArray<ParcelableKeyRing> keyData) { + return new ParcelableFileCache.IteratorWithSize<ParcelableKeyRing>() { + int i = 0; + + @Override + public int getSize() { + return keyData.size(); + } + + @Override + public boolean hasNext() { + return (i < getSize()); + } + + @Override + public ParcelableKeyRing next() { + // get the object by the key. + ParcelableKeyRing key = keyData.valueAt(i); + i++; + return key; + } + + @Override + public void remove() { + keyData.remove(i); + } + }; + } + + public void importKeys(final LongSparseArray<ParcelableKeyRing> keyData, String fingerprint) { + // Message is received after importing is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( + this, + getString(R.string.progress_importing), + ProgressDialog.STYLE_HORIZONTAL, + true) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + // get returned data bundle + Bundle returnData = message.getData(); + if (returnData == null) { + return; + } + final ImportKeyResult result = + returnData.getParcelable(OperationResult.EXTRA_RESULT); + if (result == null) { + Log.e(Constants.TAG, "result == null"); + return; + } + + Intent certifyIntent = new Intent(AddKeysActivity.this, MultiCertifyKeyActivity.class); + certifyIntent.putExtra(MultiCertifyKeyActivity.EXTRA_RESULT, result); + certifyIntent.putExtra(MultiCertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds()); + certifyIntent.putExtra(MultiCertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, mExchangeMasterKeyId); + startActivityForResult(certifyIntent, REQUEST_CODE_RESULT_TO_LIST); + } + } + }; + + if (keyData != null) { + Log.d(Constants.TAG, "importKeys started"); + + // Send all information needed to service to import key in other thread + Intent intent = new Intent(this, KeychainIntentService.class); + + intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING); + + // fill values for this action + Bundle data = new Bundle(); + + // instead of giving the entries by Intent extra, cache them into a + // file to prevent Java Binder problems on heavy imports + // read FileImportCache for more info. + try { + // We parcel this iteratively into a file - anything we can + // display here, we should be able to import. + ParcelableFileCache<ParcelableKeyRing> cache = + new ParcelableFileCache<ParcelableKeyRing>(this, "key_import.pcl"); + cache.writeCache(getSelectedData(keyData)); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(this); + + // start service with intent + startService(intent); + } catch (IOException e) { + Log.e(Constants.TAG, "Problem writing cache file", e); + Notify.showNotify(this, "Problem writing cache file!", Notify.Style.ERROR); + } + } else if (fingerprint != null) { + + // search config + Preferences prefs = Preferences.getPreferences(this); + Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); + + // Send all information needed to service to query keys in other thread + Intent intent = new Intent(this, KeychainIntentService.class); + + intent.setAction(KeychainIntentService.ACTION_DOWNLOAD_AND_IMPORT_KEYS); + + // fill values for this action + Bundle data = new Bundle(); + + data.putString(KeychainIntentService.DOWNLOAD_KEY_SERVER, cloudPrefs.keyserver); + + final ImportKeysListEntry keyEntry = new ImportKeysListEntry(); + keyEntry.setFingerprintHex(fingerprint); + keyEntry.addOrigin(cloudPrefs.keyserver); + ArrayList<ImportKeysListEntry> selectedEntries = new ArrayList<ImportKeysListEntry>(); + selectedEntries.add(keyEntry); + + data.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST, selectedEntries); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(this); + + // start service with intent + startService(intent); + } else { + Notify.showNotify(this, R.string.error_nothing_import, Notify.Style.ERROR); + } + } +} 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 5eec8454a..0c3eeece4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -48,6 +48,10 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; +import org.sufficientlysecure.keychain.service.results.CertifyResult; +import org.sufficientlysecure.keychain.service.results.SingletonResult; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -55,9 +59,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.service.results.OperationResult.LogLevel; import org.sufficientlysecure.keychain.service.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.service.results.SingletonResult; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; @@ -243,8 +245,8 @@ public class CertifyKeyFragment extends LoaderFragment String mainUserId = data.getString(INDEX_USER_ID); mInfoPrimaryUserId.setText(mainUserId); - byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT); - String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); + byte[] fp = data.getBlob(INDEX_FINGERPRINT); + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); mInfoFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint)); } break; @@ -312,27 +314,27 @@ public class CertifyKeyFragment extends LoaderFragment intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING); // fill values for this action - Bundle data = new Bundle(); - - data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId); - data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId); - data.putStringArrayList(KeychainIntentService.CERTIFY_KEY_UIDS, userIds); + CertifyActionsParcel parcel = new CertifyActionsParcel(mMasterKeyId); + parcel.add(new CertifyAction(mPubKeyId, userIds)); + Bundle data = new Bundle(); + data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); // Message is received after signing is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity, - getString(R.string.progress_certifying), ProgressDialog.STYLE_SPINNER) { + getString(R.string.progress_certifying), ProgressDialog.STYLE_SPINNER, true) { public void handleMessage(Message message) { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - SingletonResult result = new SingletonResult( - SingletonResult.RESULT_OK, LogType.MSG_CRT_SUCCESS); + Bundle data = message.getData(); + CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); + Intent intent = new Intent(); - intent.putExtra(SingletonResult.EXTRA_RESULT, result); + intent.putExtra(CertifyResult.EXTRA_RESULT, result); mActivity.setResult(CertifyKeyActivity.RESULT_OK, intent); // check if we need to send the key to the server or not diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 6f521efa2..b7d204851 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -21,16 +21,14 @@ import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.RelativeLayout; import android.widget.TextView; import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -43,14 +41,19 @@ public abstract class DecryptFragment extends Fragment { protected long mSignatureKeyId = 0; protected LinearLayout mResultLayout; - protected RelativeLayout mSignatureLayout; - protected TextView mResultText; - protected ImageView mSignatureStatusImage; - protected TextView mUserId; - protected TextView mUserIdRest; + protected ImageView mEncryptionIcon; + protected TextView mEncryptionText; + protected ImageView mSignatureIcon; + protected TextView mSignatureText; + + protected View mSignatureLayout; + protected View mSignatureDivider1; + protected View mSignatureDivider2; + protected TextView mSignatureName; + protected TextView mSignatureEmail; + protected TextView mSignatureAction; - protected Button mLookupKey; // State protected String mPassphrase; @@ -60,20 +63,20 @@ public abstract class DecryptFragment extends Fragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mResultLayout = (LinearLayout) getView().findViewById(R.id.result); - mResultText = (TextView) getView().findViewById(R.id.result_text); - mSignatureLayout = (RelativeLayout) getView().findViewById(R.id.result_signature); - mSignatureStatusImage = (ImageView) getView().findViewById(R.id.ic_signature_status); - mUserId = (TextView) getView().findViewById(R.id.mainUserId); - mUserIdRest = (TextView) getView().findViewById(R.id.mainUserIdRest); - mLookupKey = (Button) getView().findViewById(R.id.lookup_key); - mLookupKey.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - lookupUnknownKey(mSignatureKeyId); - } - }); + mResultLayout = (LinearLayout) getView().findViewById(R.id.result_main_layout); mResultLayout.setVisibility(View.GONE); + + mEncryptionIcon = (ImageView) getView().findViewById(R.id.result_encryption_icon); + mEncryptionText = (TextView) getView().findViewById(R.id.result_encryption_text); + mSignatureIcon = (ImageView) getView().findViewById(R.id.result_signature_icon); + mSignatureText = (TextView) getView().findViewById(R.id.result_signature_text); + mSignatureLayout = getView().findViewById(R.id.result_signature_layout); + mSignatureDivider1 = getView().findViewById(R.id.result_signature_divider1); + mSignatureDivider2 = getView().findViewById(R.id.result_signature_divider2); + mSignatureName = (TextView) getView().findViewById(R.id.result_signature_name); + mSignatureEmail = (TextView) getView().findViewById(R.id.result_signature_email); + mSignatureAction = (TextView) getView().findViewById(R.id.result_signature_action); + } private void lookupUnknownKey(long unknownKeyId) { @@ -83,6 +86,13 @@ public abstract class DecryptFragment extends Fragment { startActivityForResult(intent, RESULT_CODE_LOOKUP_KEY); } + private void showKey(long keyId) { + Intent viewKeyIntent = new Intent(getActivity(), ViewKeyActivity.class); + viewKeyIntent.setData(KeychainContract.KeyRings + .buildGenericKeyRingUri(keyId)); + startActivity(viewKeyIntent); + } + protected void startPassphraseDialog(long subkeyId) { Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId); @@ -102,100 +112,143 @@ public abstract class DecryptFragment extends Fragment { startActivityForResult(intent, REQUEST_CODE_NFC_DECRYPT); } - protected void onResult(DecryptVerifyResult decryptVerifyResult) { - OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult(); + /** + * + * @return returns false if signature is invalid, key is revoked or expired. + */ + protected boolean onResult(DecryptVerifyResult decryptVerifyResult) { + final OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult(); + + boolean valid = false; mSignatureKeyId = 0; mResultLayout.setVisibility(View.VISIBLE); if (signatureResult != null) { - mSignatureStatusImage.setVisibility(View.VISIBLE); - mSignatureKeyId = signatureResult.getKeyId(); String userId = signatureResult.getPrimaryUserId(); String[] userIdSplit = KeyRing.splitUserId(userId); if (userIdSplit[0] != null) { - mUserId.setText(userIdSplit[0]); + mSignatureName.setText(userIdSplit[0]); } else { - mUserId.setText(R.string.user_id_no_name); + mSignatureName.setText(R.string.user_id_no_name); } if (userIdSplit[1] != null) { - mUserIdRest.setText(userIdSplit[1]); + mSignatureEmail.setText(userIdSplit[1]); + } else { + mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(getActivity(), mSignatureKeyId)); + } + + if (signatureResult.isSignatureOnly()) { + mEncryptionText.setText(R.string.decrypt_result_not_encrypted); + KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, KeyFormattingUtils.STATE_NOT_ENCRYPTED); } else { - mUserIdRest.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(getActivity(), mSignatureKeyId)); + mEncryptionText.setText(R.string.decrypt_result_encrypted); + KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, KeyFormattingUtils.STATE_ENCRYPTED); } switch (signatureResult.getStatus()) { case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: { - if (signatureResult.isSignatureOnly()) { - mResultText.setText(R.string.decrypt_result_signature_certified); - } else { - mResultText.setText(R.string.decrypt_result_decrypted_and_signature_certified); - } - - mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_green_light)); - mSignatureStatusImage.setImageResource(R.drawable.overlay_ok); - mSignatureLayout.setVisibility(View.VISIBLE); - mLookupKey.setVisibility(View.GONE); + mSignatureText.setText(R.string.decrypt_result_signature_certified); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_VERIFIED); + + setSignatureLayoutVisibility(View.VISIBLE); + setShowAction(mSignatureKeyId); + + valid = true; break; } case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: { - if (signatureResult.isSignatureOnly()) { - mResultText.setText(R.string.decrypt_result_signature_uncertified); - } else { - mResultText.setText(R.string.decrypt_result_decrypted_and_signature_uncertified); - } - - mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); - mSignatureStatusImage.setImageResource(R.drawable.overlay_ok); - mSignatureLayout.setVisibility(View.VISIBLE); - mLookupKey.setVisibility(View.GONE); + mSignatureText.setText(R.string.decrypt_result_signature_uncertified); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_UNVERIFIED); + + setSignatureLayoutVisibility(View.VISIBLE); + setShowAction(mSignatureKeyId); + + valid = true; break; } case OpenPgpSignatureResult.SIGNATURE_KEY_MISSING: { - if (signatureResult.isSignatureOnly()) { - mResultText.setText(R.string.decrypt_result_signature_unknown_pub_key); - } else { - mResultText.setText(R.string.decrypt_result_decrypted_unknown_pub_key); - } - - mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); - mSignatureStatusImage.setImageResource(R.drawable.overlay_error); - mSignatureLayout.setVisibility(View.VISIBLE); - mLookupKey.setVisibility(View.VISIBLE); + mSignatureText.setText(R.string.decrypt_result_signature_missing_key); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_UNKNOWN_KEY); + + setSignatureLayoutVisibility(View.VISIBLE); + mSignatureAction.setText(R.string.decrypt_result_action_Lookup); + mSignatureAction.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_action_download, 0); + mSignatureLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + lookupUnknownKey(mSignatureKeyId); + } + }); + + valid = true; break; } - case OpenPgpSignatureResult.SIGNATURE_ERROR: { - mResultText.setText(R.string.decrypt_result_invalid_signature); + case OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED: { + mSignatureText.setText(R.string.decrypt_result_signature_expired_key); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_EXPIRED); + + setSignatureLayoutVisibility(View.VISIBLE); + setShowAction(mSignatureKeyId); + + valid = false; + break; + } - mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_red_light)); - mSignatureStatusImage.setImageResource(R.drawable.overlay_error); - mSignatureLayout.setVisibility(View.GONE); - mLookupKey.setVisibility(View.GONE); + case OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED: { + mSignatureText.setText(R.string.decrypt_result_signature_revoked_key); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_REVOKED); + + setSignatureLayoutVisibility(View.VISIBLE); + setShowAction(mSignatureKeyId); + + valid = false; break; } - default: { - mResultText.setText(R.string.error); + case OpenPgpSignatureResult.SIGNATURE_ERROR: { + mSignatureText.setText(R.string.decrypt_result_invalid_signature); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_INVALID); - mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_red_light)); - mSignatureStatusImage.setImageResource(R.drawable.overlay_error); - mSignatureLayout.setVisibility(View.GONE); - mLookupKey.setVisibility(View.GONE); + setSignatureLayoutVisibility(View.GONE); + + valid = false; break; } } } else { - mSignatureLayout.setVisibility(View.GONE); - mLookupKey.setVisibility(View.GONE); + setSignatureLayoutVisibility(View.GONE); + + mSignatureText.setText(R.string.decrypt_result_no_signature); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_NOT_SIGNED); + mEncryptionText.setText(R.string.decrypt_result_encrypted); + KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, KeyFormattingUtils.STATE_ENCRYPTED); - // successful decryption-only - mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_purple_light)); - mResultText.setText(R.string.decrypt_result_decrypted); + valid = true; } + + return valid; + } + + private void setSignatureLayoutVisibility(int visibility) { + mSignatureLayout.setVisibility(visibility); + mSignatureDivider1.setVisibility(visibility); + mSignatureDivider2.setVisibility(visibility); + } + + private void setShowAction(final long signatureKeyId) { + mSignatureAction.setText(R.string.decrypt_result_action_show); + mSignatureAction.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_action_accounts, 0); + mSignatureLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showKey(signatureKeyId); + } + }); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java index 73163eabd..28010884b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; +import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -78,29 +79,41 @@ public class DecryptTextActivity extends ActionBarActivity { /** * Fixing broken PGP SIGNED MESSAGE Strings coming from GMail/AOSP Mail */ - private String fixPgpCleartextSignature(String message) { - // windows newline -> unix newline - message = message.replaceAll("\r\n", "\n"); - // Mac OS before X newline -> unix newline - message = message.replaceAll("\r", "\n"); + private String fixPgpCleartextSignature(CharSequence input) { + if (!TextUtils.isEmpty(input)) { + String text = input.toString(); - return message; + // windows newline -> unix newline + text = text.replaceAll("\r\n", "\n"); + // Mac OS before X newline -> unix newline + text = text.replaceAll("\r", "\n"); + + return text; + } else { + return null; + } } - private String getPgpContent(String input) { + private String getPgpContent(CharSequence input) { // only decrypt if clipboard content is available and a pgp message or cleartext signature - if (input != null) { + if (!TextUtils.isEmpty(input)) { + Log.dEscaped(Constants.TAG, "input: " + input); + Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input); if (matcher.matches()) { - String message = matcher.group(1); - message = fixPgpMessage(message); - return message; + String text = matcher.group(1); + text = fixPgpMessage(text); + + Log.dEscaped(Constants.TAG, "input fixed: " + text); + return text; } else { matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input); if (matcher.matches()) { - String message = matcher.group(1); - message = fixPgpCleartextSignature(message); - return message; + String text = matcher.group(1); + text = fixPgpCleartextSignature(text); + + Log.dEscaped(Constants.TAG, "input fixed: " + text); + return text; } else { return null; } @@ -125,14 +138,13 @@ public class DecryptTextActivity extends ActionBarActivity { } if (Intent.ACTION_SEND.equals(action) && type != null) { - Log.logDebugBundle(extras, "extras"); + Log.d(Constants.TAG, "ACTION_SEND"); + Log.logDebugBundle(extras, "SEND extras"); // When sending to Keychain Decrypt via share menu if ("text/plain".equals(type)) { String sharedText = extras.getString(Intent.EXTRA_TEXT); - Log.dEscaped(Constants.TAG, "sharedText incoming: " + sharedText); sharedText = getPgpContent(sharedText); - Log.dEscaped(Constants.TAG, "sharedText fixed: " + sharedText); if (sharedText != null) { loadFragment(savedInstanceState, sharedText); @@ -143,7 +155,7 @@ public class DecryptTextActivity extends ActionBarActivity { Log.e(Constants.TAG, "ACTION_SEND received non-plaintext, this should not happen in this activity!"); } } else if (ACTION_DECRYPT_TEXT.equals(action)) { - Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT textData not null, matching text..."); + Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT"); String extraText = extras.getString(EXTRA_TEXT); extraText = getPgpContent(extraText); @@ -157,9 +169,9 @@ public class DecryptTextActivity extends ActionBarActivity { Log.d(Constants.TAG, "ACTION_DECRYPT_FROM_CLIPBOARD"); CharSequence clipboardText = ClipboardReflection.getClipboardText(this); + String text = getPgpContent(clipboardText); - if (clipboardText != null) { - String text = getPgpContent(clipboardText.toString()); + if (text != null) { loadFragment(savedInstanceState, text); } else { returnInvalidResult(); 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 4f25126ee..78345e11c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java @@ -27,6 +27,8 @@ import android.text.method.ScrollingMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpApi; @@ -44,6 +46,9 @@ public class DecryptTextFragment extends DecryptFragment { public static final String ARG_CIPHERTEXT = "ciphertext"; // view + private LinearLayout mValidLayout; + private LinearLayout mInvalidLayout; + private Button mInvalidButton; private TextView mText; private View mShareButton; private View mCopyButton; @@ -71,7 +76,9 @@ public class DecryptTextFragment extends DecryptFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false); - + mValidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_valid); + mInvalidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_invalid); + mInvalidButton = (Button) view.findViewById(R.id.decrypt_text_invalid_button); mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext); mShareButton = view.findViewById(R.id.action_decrypt_share_plaintext); mCopyButton = view.findViewById(R.id.action_decrypt_copy_plaintext); @@ -87,6 +94,13 @@ public class DecryptTextFragment extends DecryptFragment { copyToClipboard(mText.getText().toString()); } }); + mInvalidButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mInvalidLayout.setVisibility(View.GONE); + mValidLayout.setVisibility(View.VISIBLE); + } + }); return view; } @@ -186,9 +200,18 @@ public class DecryptTextFragment extends DecryptFragment { pgpResult.createNotify(getActivity()).show(); // display signature result in activity - onResult(pgpResult); + boolean valid = onResult(pgpResult); + + if (valid) { + mInvalidLayout.setVisibility(View.GONE); + mValidLayout.setVisibility(View.VISIBLE); + } else { + mInvalidLayout.setVisibility(View.VISIBLE); + mValidLayout.setVisibility(View.GONE); + } } else { pgpResult.createNotify(getActivity()).show(); + // TODO: show also invalid layout with different text? } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java index 7132518ae..da46de486 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java @@ -82,7 +82,7 @@ public class DrawerActivity extends ActionBarActivity { } NavItem mItemIconTexts[] = new NavItem[]{ - new NavItem(R.drawable.ic_action_person, getString(R.string.nav_keys)), + new NavItem(R.drawable.ic_action_accounts, getString(R.string.nav_keys)), new NavItem(R.drawable.ic_action_secure, getString(R.string.nav_encrypt_text)), new NavItem(R.drawable.ic_action_secure, getString(R.string.nav_encrypt_files)), new NavItem(R.drawable.ic_action_not_secure, getString(R.string.nav_decrypt)), 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 efad2e45d..f0bd2c76c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -97,7 +97,6 @@ public class ImportKeysActivity extends ActionBarActivity { private SlidingTabLayout mSlidingTabLayout; private PagerTabStripAdapter mTabsAdapter; - public static final int VIEW_PAGER_HEIGHT = 64; // dp private static final int ALL_TABS = -1; private static final int TAB_CLOUD = 0; @@ -151,12 +150,13 @@ public class ImportKeysActivity extends ActionBarActivity { } Bundle serverBundle = null; - int showTabOnly = ALL_TABS; + int showTabOnly = TAB_CLOUD; if (ACTION_IMPORT_KEY.equals(action)) { /* Keychain's own Actions */ // display file fragment - mViewPager.setCurrentItem(TAB_FILE); + showTabOnly = TAB_FILE; + mSwitchToTab = TAB_FILE; if (dataUri != null) { // action: directly load data @@ -230,6 +230,7 @@ public class ImportKeysActivity extends ActionBarActivity { } } else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) { // NOTE: this only displays the appropriate fragment, no actions are taken + showTabOnly = TAB_FILE; mSwitchToTab = TAB_FILE; // no immediate actions! @@ -244,14 +245,14 @@ public class ImportKeysActivity extends ActionBarActivity { startListFragment(savedInstanceState, null, null, null); } else if (ACTION_IMPORT_KEY_FROM_QR_CODE.equals(action)) { // also exposed in AndroidManifest - + showTabOnly = ALL_TABS; // NOTE: this only displays the appropriate fragment, no actions are taken mSwitchToTab = TAB_QR_CODE; // no immediate actions! startListFragment(savedInstanceState, null, null, null); } else if (ACTION_IMPORT_KEY_FROM_NFC.equals(action)) { - + showTabOnly = ALL_TABS; // NOTE: this only displays the appropriate fragment, no actions are taken mSwitchToTab = TAB_QR_CODE; @@ -270,10 +271,6 @@ public class ImportKeysActivity extends ActionBarActivity { mSlidingTabLayout.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - // resize view pager back to 64 if keyserver settings have been collapsed - if (getViewPagerHeight() > VIEW_PAGER_HEIGHT) { - resizeViewPager(VIEW_PAGER_HEIGHT); - } } @Override @@ -339,44 +336,36 @@ public class ImportKeysActivity extends ActionBarActivity { getSupportFragmentManager().executePendingTransactions(); } - public void resizeViewPager(int dp) { - ViewGroup.LayoutParams params = mViewPager.getLayoutParams(); - params.height = FormattingUtils.dpToPx(this, dp); - // update layout after operations - mSlidingTabLayout.setViewPager(mViewPager); - } - - public int getViewPagerHeight() { - ViewGroup.LayoutParams params = mViewPager.getLayoutParams(); - return FormattingUtils.pxToDp(this, params.height); - } - - private String getFingerprintFromUri(Uri dataUri) { + public static String getFingerprintFromUri(Uri dataUri) { String fingerprint = dataUri.toString().split(":")[1].toLowerCase(Locale.ENGLISH); Log.d(Constants.TAG, "fingerprint: " + fingerprint); return fingerprint; } - public void loadFromFingerprintUri(Uri dataUri) { - String query = "0x" + getFingerprintFromUri(dataUri); + public void loadFromFingerprint(String fingerprint) { +// String fingerprint = "0x" + getFingerprintFromUri(dataUri); // setCurrentItem does not work directly after onResume (from qr code scanner) // see http://stackoverflow.com/q/19316729 // so, reset adapter completely! - if (mViewPager.getAdapter() != null) - mViewPager.setAdapter(null); - mViewPager.setAdapter(mTabsAdapter); - mViewPager.setCurrentItem(TAB_CLOUD); - - ImportKeysCloudFragment f = (ImportKeysCloudFragment) - getActiveFragment(mViewPager, TAB_CLOUD); - - // search config - Preferences prefs = Preferences.getPreferences(ImportKeysActivity.this); - Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - - // search directly - loadCallback(new ImportKeysListFragment.CloudLoaderState(query, cloudPrefs)); +// if (mViewPager.getAdapter() != null) +// mViewPager.setAdapter(null); +// mViewPager.setAdapter(mTabsAdapter); +// mViewPager.setCurrentItem(TAB_CLOUD); + +// ImportKeysCloudFragment f = (ImportKeysCloudFragment) +// getActiveFragment(mViewPager, TAB_CLOUD); + + Intent searchIntent = new Intent(this, ImportKeysActivity.class); + searchIntent.putExtra(ImportKeysActivity.EXTRA_FINGERPRINT, fingerprint); + startActivity(searchIntent); + +// // search config +// Preferences prefs = Preferences.getPreferences(ImportKeysActivity.this); +// Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); +// +// // search directly +// loadCallback(new ImportKeysListFragment.CloudLoaderState(query, cloudPrefs)); } // http://stackoverflow.com/a/9293207 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java index e5bad16ce..538fa16c7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -31,8 +31,6 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.util.FileHelper; -import java.util.Locale; - public class ImportKeysFileFragment extends Fragment { private ImportKeysActivity mImportActivity; private View mBrowse; @@ -80,12 +78,8 @@ public class ImportKeysFileFragment extends Fragment { String sendText = ""; if (clipboardText != null) { sendText = clipboardText.toString(); - if (sendText.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) { - mImportActivity.loadFromFingerprintUri(Uri.parse(sendText)); - return; - } + mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(sendText.getBytes(), null)); } - mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(sendText.getBytes(), null)); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index 16ebd6aca..ad50ecd3d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -31,6 +31,7 @@ import android.widget.ListView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.results.GetKeyResult; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; @@ -285,48 +286,60 @@ public class ImportKeysListFragment extends ListFragment implements setListShownNoAnimation(true); } - Exception error = data.getError(); - // free old cached key data mCachedKeyData = null; + GetKeyResult getKeyResult = (GetKeyResult) data.getOperationResult(); switch (loader.getId()) { case LOADER_ID_BYTES: - if (error == null) { + if (getKeyResult.success()) { // No error mCachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings(); - } else if (error instanceof ImportKeysListLoader.NoValidKeysException) { - Notify.showNotify(getActivity(), R.string.error_import_no_valid_keys, Notify.Style.ERROR); - } else if (error instanceof ImportKeysListLoader.NonPgpPartException) { - Notify.showNotify(getActivity(), - ((ImportKeysListLoader.NonPgpPartException) error).getCount() + " " + getResources(). - getQuantityString(R.plurals.error_import_non_pgp_part, - ((ImportKeysListLoader.NonPgpPartException) error).getCount()), - Notify.Style.OK - ); } else { - Notify.showNotify(getActivity(), R.string.error_generic_report_bug, Notify.Style.ERROR); + getKeyResult.createNotify(getActivity()).show(); } +// if (error == null) { +// // No error +// mCachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings(); +// } else if (error instanceof ImportKeysListLoader.NoValidKeysException) { +// Notify.showNotify(getActivity(), R.string.error_import_no_valid_keys, Notify.Style.ERROR); +// } else if (error instanceof ImportKeysListLoader.NonPgpPartException) { +// Notify.showNotify(getActivity(), +// ((ImportKeysListLoader.NonPgpPartException) error).getCount() + " " + getResources(). +// getQuantityString(R.plurals.error_import_non_pgp_part, +// ((ImportKeysListLoader.NonPgpPartException) error).getCount()), +// Notify.Style.OK +// ); +// } else { +// Notify.showNotify(getActivity(), R.string.error_generic_report_bug, Notify.Style.ERROR); +// } break; case LOADER_ID_CLOUD: - if (error == null) { + if (getKeyResult.success()) { // No error - } else if (error instanceof Keyserver.QueryTooShortException) { - Notify.showNotify(getActivity(), R.string.error_query_too_short, Notify.Style.ERROR); - } else if (error instanceof Keyserver.TooManyResponsesException) { - Notify.showNotify(getActivity(), R.string.error_too_many_responses, Notify.Style.ERROR); - } else if (error instanceof Keyserver.QueryTooShortOrTooManyResponsesException) { - Notify.showNotify(getActivity(), R.string.error_too_short_or_too_many_responses, Notify.Style.ERROR); - } else if (error instanceof Keyserver.QueryFailedException) { - Log.d(Constants.TAG, - "Unrecoverable keyserver query error: " + error.getLocalizedMessage()); - String alert = getActivity().getString(R.string.error_searching_keys); - alert = alert + " (" + error.getLocalizedMessage() + ")"; - Notify.showNotify(getActivity(), alert, Notify.Style.ERROR); + } else { + getKeyResult.createNotify(getActivity()).show(); } + + +// if (error == null) { +// // No error +// } else if (error instanceof Keyserver.QueryTooShortException) { +// Notify.showNotify(getActivity(), R.string.error_query_too_short, Notify.Style.ERROR); +// } else if (error instanceof Keyserver.TooManyResponsesException) { +// Notify.showNotify(getActivity(), R.string.error_too_many_responses, Notify.Style.ERROR); +// } else if (error instanceof Keyserver.QueryTooShortOrTooManyResponsesException) { +// Notify.showNotify(getActivity(), R.string.error_too_short_or_too_many_responses, Notify.Style.ERROR); +// } else if (error instanceof Keyserver.QueryFailedException) { +// Log.d(Constants.TAG, +// "Unrecoverable keyserver query error: " + error.getLocalizedMessage()); +// String alert = getActivity().getString(R.string.error_searching_keys); +// alert = alert + " (" + error.getLocalizedMessage() + ")"; +// Notify.showNotify(getActivity(), alert, Notify.Style.ERROR); +// } break; default: diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java index a52737979..fb4bbfac4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java @@ -132,7 +132,7 @@ public class ImportKeysQrCodeFragment extends Fragment { } public void importFingerprint(Uri dataUri) { - mImportActivity.loadFromFingerprintUri(dataUri); + mImportActivity.loadFromFingerprint(ImportKeysActivity.getFingerprintFromUri(dataUri)); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index f06531b01..019ee82ec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -28,16 +28,16 @@ import android.view.MenuItem; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.ExportHelper; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.service.results.OperationResult; import org.sufficientlysecure.keychain.service.results.ConsolidateResult; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.service.results.OperationResult; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.ExportHelper; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; @@ -49,6 +49,8 @@ public class KeyListActivity extends DrawerActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setTitle(R.string.nav_keys); + // if this is the first time show first time activity Preferences prefs = Preferences.getPreferences(this); if (prefs.isFirstTime()) { @@ -84,7 +86,11 @@ public class KeyListActivity extends DrawerActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_key_list_add: - importKeys(); + addKeys(); + return true; + + case R.id.menu_key_list_search_cloud: + searchCloud(); return true; case R.id.menu_key_list_create: @@ -139,11 +145,17 @@ public class KeyListActivity extends DrawerActivity { } } - private void importKeys() { - Intent intent = new Intent(this, ImportKeysActivity.class); + private void addKeys() { + Intent intent = new Intent(this, AddKeysActivity.class); startActivityForResult(intent, 0); } + private void searchCloud() { + Intent importIntent = new Intent(this, ImportKeysActivity.class); + importIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, (String) null); // hack to show only cloud tab + startActivity(importIntent); + } + private void createKey() { Intent intent = new Intent(this, CreateKeyActivity.class); startActivityForResult(intent, 0); 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 02397538c..4024ee7fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -49,9 +49,11 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; +import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.ListView; +import android.widget.Spinner; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; @@ -59,7 +61,6 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.KeyUpdateHelper; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; @@ -69,8 +70,10 @@ import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.ui.util.Notify; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.List; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; @@ -86,10 +89,13 @@ public class KeyListFragment extends LoaderFragment private KeyListAdapter mAdapter; private StickyListHeadersListView mStickyList; private ListAwareSwipeRefreshLayout mSwipeRefreshLayout; + private Spinner mFilterSpinner; // saves the mode object for multiselect, needed for reset at some point private ActionMode mActionMode = null; + private boolean mShowAllKeys = false; + private String mQuery; private SearchView mSearchView; // empty list layout @@ -214,6 +220,42 @@ public class KeyListFragment extends LoaderFragment public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + mFilterSpinner = (Spinner) getActivity().findViewById(R.id.key_list_filter_spinner); + List<String> list = new ArrayList<String>(); + list.add(getString(R.string.key_list_filter_show_certified)); + list.add(getString(R.string.key_list_filter_show_all)); + + ArrayAdapter<String> dataAdapter = new ArrayAdapter<String> + (getActivity(), android.R.layout.simple_spinner_item, list); + + dataAdapter.setDropDownViewResource + (android.R.layout.simple_spinner_dropdown_item); + + mFilterSpinner.setAdapter(dataAdapter); + + mFilterSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + switch (position) { + case 0: { + mShowAllKeys = false; + getLoaderManager().restartLoader(0, null, KeyListFragment.this); + break; + } + case 1: { + mShowAllKeys = true; + getLoaderManager().restartLoader(0, null, KeyListFragment.this); + break; + } + } + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + + } + }); + mStickyList.setOnItemClickListener(this); mStickyList.setAreHeadersSticky(true); mStickyList.setDrawingListUnderStickyHeader(false); @@ -356,6 +398,14 @@ public class KeyListFragment extends LoaderFragment whereArgs[i] = "%" + words[i] + "%"; } } + if (!mShowAllKeys) { + if (where == null) { + where = ""; + } else { + where += " AND "; + } + where += KeyRings.VERIFIED + " != 0"; + } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. @@ -471,6 +521,11 @@ public class KeyListFragment extends LoaderFragment MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + hideMenu = true; + getActivity().invalidateOptionsMenu(); + } + // disable swipe-to-refresh // mSwipeRefreshLayout.setIsLocked(true); return true; @@ -480,6 +535,11 @@ public class KeyListFragment extends LoaderFragment public boolean onMenuItemActionCollapse(MenuItem item) { mQuery = null; getLoaderManager().restartLoader(0, null, KeyListFragment.this); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + hideMenu = false; + getActivity().invalidateOptionsMenu(); + } // enable swipe-to-refresh // mSwipeRefreshLayout.setIsLocked(false); return true; @@ -553,7 +613,7 @@ public class KeyListFragment extends LoaderFragment ItemViewHolder holder = new ItemViewHolder(); holder.mMainUserId = (TextView) view.findViewById(R.id.mainUserId); holder.mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - holder.mStatus = (ImageView) view.findViewById(R.id.status_image); + holder.mStatus = (ImageView) view.findViewById(R.id.status_icon); view.setTag(holder); return view; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index 4fdfd3a21..0f59b6d6a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -24,36 +24,27 @@ import android.graphics.Color; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.util.TypedValue; -import android.view.GestureDetector; -import android.view.GestureDetector.SimpleOnGestureListener; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; -import android.view.View.OnTouchListener; import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.results.OperationResult; import org.sufficientlysecure.keychain.service.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.service.results.OperationResult.LogLevel; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.service.results.OperationResult.SubLogEntryParcel; -import java.util.HashMap; +public class LogDisplayFragment extends ListFragment implements OnItemClickListener { -public class LogDisplayFragment extends ListFragment implements OnTouchListener { - - HashMap<LogLevel,LogAdapter> mAdapters = new HashMap<LogLevel, LogAdapter>(); LogAdapter mAdapter; - LogLevel mLevel = LogLevel.DEBUG; OperationResult mResult; - GestureDetector mDetector; - public static final String EXTRA_RESULT = "log"; @Override @@ -72,58 +63,25 @@ public class LogDisplayFragment extends ListFragment implements OnTouchListener return; } - mAdapter = new LogAdapter(getActivity(), mResult.getLog(), LogLevel.DEBUG); - mAdapters.put(LogLevel.DEBUG, mAdapter); + mAdapter = new LogAdapter(getActivity(), mResult.getLog()); setListAdapter(mAdapter); - mDetector = new GestureDetector(getActivity(), new SimpleOnGestureListener() { - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float vx, float vy) { - Log.d(Constants.TAG, "x: " + vx + ", y: " + vy); - if (vx < -2000) { - decreaseLogLevel(); - } else if (vx > 2000) { - increaseLogLevel(); - } - return true; - } - }); + getListView().setOnItemClickListener(this); getListView().setFastScrollEnabled(true); getListView().setDividerHeight(0); - getListView().setOnTouchListener(this); - } - - public void decreaseLogLevel() { - switch (mLevel) { - case DEBUG: mLevel = LogLevel.INFO; break; - case INFO: mLevel = LogLevel.WARN; break; - } - refreshLevel(); - } - - public void increaseLogLevel() { - switch (mLevel) { - case INFO: mLevel = LogLevel.DEBUG; break; - case WARN: mLevel = LogLevel.INFO; break; - } - refreshLevel(); - } - - private void refreshLevel() { - /* TODO not sure if this is a good idea - if (!mAdapters.containsKey(mLevel)) { - mAdapters.put(mLevel, new LogAdapter(getActivity(), mResult.getLog(), mLevel)); - } - mAdapter = mAdapters.get(mLevel); - setListAdapter(mAdapter); - */ } @Override - public boolean onTouch(View v, MotionEvent event) { - mDetector.onTouchEvent(event); - return false; + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + LogEntryParcel parcel = mAdapter.getItem(position); + if ( ! (parcel instanceof SubLogEntryParcel)) { + return; + } + Intent intent = new Intent( + getActivity(), LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ((SubLogEntryParcel) parcel).getSubResult()); + startActivity(intent); } private class LogAdapter extends ArrayAdapter<LogEntryParcel> { @@ -131,26 +89,24 @@ public class LogDisplayFragment extends ListFragment implements OnTouchListener private LayoutInflater mInflater; private int dipFactor; - public LogAdapter(Context context, OperationResult.OperationLog log, LogLevel level) { - super(context, R.layout.log_display_item); + public LogAdapter(Context context, OperationResult.OperationLog log) { + super(context, R.layout.log_display_item, log.toList()); mInflater = LayoutInflater.from(getContext()); dipFactor = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (float) 8, getResources().getDisplayMetrics()); - // we can't use addAll for a LogLevel.DEBUG shortcut here, unfortunately :( - for (LogEntryParcel e : log) { - if (e.mType.mLevel.ordinal() >= level.ordinal()) { - add(e); - } - } - notifyDataSetChanged(); } private class ItemHolder { - final TextView mText; - final ImageView mImg; - public ItemHolder(TextView text, ImageView image) { + final View mSecond; + final TextView mText, mSecondText; + final ImageView mImg, mSecondImg, mSub; + public ItemHolder(TextView text, ImageView image, ImageView sub, View second, TextView secondText, ImageView secondImg) { mText = text; mImg = image; + mSub = sub; + mSecond = second; + mSecondText = secondText; + mSecondImg = secondImg; } } @@ -162,13 +118,55 @@ public class LogDisplayFragment extends ListFragment implements OnTouchListener convertView = mInflater.inflate(R.layout.log_display_item, parent, false); ih = new ItemHolder( (TextView) convertView.findViewById(R.id.log_text), - (ImageView) convertView.findViewById(R.id.log_img) + (ImageView) convertView.findViewById(R.id.log_img), + (ImageView) convertView.findViewById(R.id.log_sub), + convertView.findViewById(R.id.log_second), + (TextView) convertView.findViewById(R.id.log_second_text), + (ImageView) convertView.findViewById(R.id.log_second_img) ); convertView.setTag(ih); } else { ih = (ItemHolder) convertView.getTag(); } + if (entry instanceof SubLogEntryParcel) { + ih.mSub.setVisibility(View.VISIBLE); + convertView.setClickable(false); + + OperationResult result = ((SubLogEntryParcel) entry).getSubResult(); + LogEntryParcel subEntry = result.getLog().getLast(); + if (subEntry != null) { + ih.mSecond.setVisibility(View.VISIBLE); + // special case: first parameter may be a quantity + if (subEntry.mParameters != null && subEntry.mParameters.length > 0 + && subEntry.mParameters[0] instanceof Integer) { + ih.mSecondText.setText(getResources().getQuantityString(subEntry.mType.getMsgId(), + (Integer) subEntry.mParameters[0], + subEntry.mParameters)); + } else { + ih.mSecondText.setText(getResources().getString(subEntry.mType.getMsgId(), + subEntry.mParameters)); + } + ih.mSecondText.setTextColor(subEntry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK); + switch (subEntry.mType.mLevel) { + case DEBUG: ih.mSecondImg.setBackgroundColor(Color.GRAY); break; + case INFO: ih.mSecondImg.setBackgroundColor(Color.BLACK); break; + case WARN: ih.mSecondImg.setBackgroundColor(Color.YELLOW); break; + case ERROR: ih.mSecondImg.setBackgroundColor(Color.RED); break; + case START: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.emphasis)); break; + case OK: ih.mSecondImg.setBackgroundColor(Color.GREEN); break; + case CANCELLED: ih.mSecondImg.setBackgroundColor(Color.RED); break; + } + } else { + ih.mSecond.setVisibility(View.GONE); + } + + } else { + ih.mSub.setVisibility(View.GONE); + ih.mSecond.setVisibility(View.GONE); + convertView.setClickable(true); + } + // special case: first parameter may be a quantity if (entry.mParameters != null && entry.mParameters.length > 0 && entry.mParameters[0] instanceof Integer) { @@ -179,14 +177,14 @@ public class LogDisplayFragment extends ListFragment implements OnTouchListener ih.mText.setText(getResources().getString(entry.mType.getMsgId(), entry.mParameters)); } - ih.mText.setTextColor(entry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK); convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0); + ih.mText.setTextColor(entry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK); switch (entry.mType.mLevel) { case DEBUG: ih.mImg.setBackgroundColor(Color.GRAY); break; case INFO: ih.mImg.setBackgroundColor(Color.BLACK); break; case WARN: ih.mImg.setBackgroundColor(Color.YELLOW); break; case ERROR: ih.mImg.setBackgroundColor(Color.RED); break; - case START: ih.mImg.setBackgroundColor(Color.GREEN); break; + case START: ih.mImg.setBackgroundColor(getResources().getColor(R.color.emphasis)); break; case OK: ih.mImg.setBackgroundColor(Color.GREEN); break; case CANCELLED: ih.mImg.setBackgroundColor(Color.RED); break; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiCertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiCertifyKeyActivity.java new file mode 100644 index 000000000..64853519d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiCertifyKeyActivity.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2011 Senecaso + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.os.Bundle; +import android.support.v7.app.ActionBarActivity; + +import org.sufficientlysecure.keychain.R; + +/** + * Signs the specified public key with the specified secret master key + */ +public class MultiCertifyKeyActivity extends ActionBarActivity { + + public static final String EXTRA_RESULT = "operation_result"; + public static final String EXTRA_KEY_IDS = "extra_key_ids"; + public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.multi_certify_key_activity); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiCertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiCertifyKeyFragment.java new file mode 100644 index 000000000..ddfbac03c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiCertifyKeyFragment.java @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 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.ui; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.graphics.PorterDuff; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.os.Parcel; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.ListView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.service.results.CertifyResult; +import org.sufficientlysecure.keychain.service.results.OperationResult; +import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; +import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; +import org.sufficientlysecure.keychain.ui.widget.KeySpinner; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; + +public class MultiCertifyKeyFragment extends LoaderFragment + implements LoaderManager.LoaderCallbacks<Cursor> { + + private FragmentActivity mActivity; + + private CheckBox mUploadKeyCheckbox; + ListView mUserIds; + + private CertifyKeySpinner mCertifyKeySpinner; + + private long[] mPubMasterKeyIds; + + private long mSignMasterKeyId = Constants.key.none; + + public static final String[] USER_IDS_PROJECTION = new String[]{ + UserIds._ID, + UserIds.MASTER_KEY_ID, + UserIds.USER_ID, + UserIds.IS_PRIMARY, + UserIds.IS_REVOKED + }; + private static final int INDEX_MASTER_KEY_ID = 1; + private static final int INDEX_USER_ID = 2; + private static final int INDEX_IS_PRIMARY = 3; + private static final int INDEX_IS_REVOKED = 4; + + private MultiUserIdsAdapter mUserIdsAdapter; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + // Start out with a progress indicator. + setContentShown(false); + + mPubMasterKeyIds = mActivity.getIntent().getLongArrayExtra(MultiCertifyKeyActivity.EXTRA_KEY_IDS); + if (mPubMasterKeyIds == null) { + Log.e(Constants.TAG, "List of key ids to certify missing!"); + mActivity.finish(); + return; + } + + // preselect certify key id if given + long certifyKeyId = mActivity.getIntent().getLongExtra(MultiCertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); + if (certifyKeyId != Constants.key.none) { + try { + CachedPublicKeyRing key = (new ProviderHelper(getActivity())).getCachedPublicKeyRing(certifyKeyId); + if (key.canCertify()) { + mCertifyKeySpinner.setSelectedKeyId(certifyKeyId); + } + } catch (PgpGeneralException e) { + Log.e(Constants.TAG, "certify certify check failed", e); + } + } + + mUserIdsAdapter = new MultiUserIdsAdapter(mActivity, null, 0); + mUserIds.setAdapter(mUserIdsAdapter); + mUserIds.setDividerHeight(0); + + getLoaderManager().initLoader(0, null, this); + + OperationResult result = mActivity.getIntent().getParcelableExtra(MultiCertifyKeyActivity.EXTRA_RESULT); + if (result != null) { + // display result from import + result.createNotify(mActivity).show(); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + + // is this "the android way"? + mActivity = getActivity(); + + View view = inflater.inflate(R.layout.multi_certify_key_fragment, getContainer()); + + mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner); + mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox); + mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + + // make certify image gray, like action icons + ImageView vActionCertifyImage = + (ImageView) view.findViewById(R.id.certify_key_action_certify_image); + vActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light), + PorterDuff.Mode.SRC_IN); + + mCertifyKeySpinner.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() { + @Override + public void onKeyChanged(long masterKeyId) { + mSignMasterKeyId = masterKeyId; + } + }); + + View vCertifyButton = view.findViewById(R.id.certify_key_certify_button); + vCertifyButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (mSignMasterKeyId == Constants.key.none) { + Notify.showNotify(mActivity, getString(R.string.select_key_to_certify), + Notify.Style.ERROR); + } else { + initiateCertifying(); + } + } + }); + + return root; + } + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + Uri uri = UserIds.buildUserIdsUri(); + + String selection, ids[]; + { + // generate placeholders and string selection args + ids = new String[mPubMasterKeyIds.length]; + StringBuilder placeholders = new StringBuilder("?"); + for (int i = 0; i < mPubMasterKeyIds.length; i++) { + ids[i] = Long.toString(mPubMasterKeyIds[i]); + if (i != 0) { + placeholders.append(",?"); + } + } + // put together selection string + selection = UserIds.IS_REVOKED + " = 0" + " AND " + + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + + " IN (" + placeholders + ")"; + } + + return new CursorLoader(mActivity, uri, + USER_IDS_PROJECTION, selection, ids, + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " ASC" + + ", " + Tables.USER_IDS + "." + UserIds.USER_ID + " ASC" + ); + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + + MatrixCursor matrix = new MatrixCursor(new String[]{ + "_id", "user_data", "grouped" + }); + data.moveToFirst(); + + long lastMasterKeyId = 0; + String lastName = ""; + ArrayList<String> uids = new ArrayList<String>(); + + 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); + String[] pieces = KeyRing.splitUserId(userId); + + // Two cases: + + boolean grouped = masterKeyId == lastMasterKeyId; + boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces[0]); + // Remember for next loop + lastName = pieces[0]; + + 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); + setContentShown(true, isResumed()); + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + mUserIdsAdapter.swapCursor(null); + } + + /** + * handles the UI bits of the signing process on the UI thread + */ + private void initiateCertifying() { + // get the user's passphrase for this key (if required) + String passphrase; + try { + passphrase = PassphraseCacheService.getCachedPassphrase(mActivity, mSignMasterKeyId, mSignMasterKeyId); + } catch (PassphraseCacheService.KeyNotFoundException e) { + Log.e(Constants.TAG, "Key not found!", e); + mActivity.finish(); + return; + } + if (passphrase == null) { + PassphraseDialogFragment.show(mActivity, mSignMasterKeyId, + new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + startCertifying(); + } + } + } + ); + // bail out; need to wait until the user has entered the passphrase before trying again + } else { + startCertifying(); + } + } + + /** + * kicks off the actual signing process on a background thread + */ + private void startCertifying() { + // Bail out if there is not at least one user id selected + ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions(); + if (certifyActions.isEmpty()) { + Notify.showNotify(mActivity, "No identities selected!", + Notify.Style.ERROR); + return; + } + + // Send all information needed to service to sign key in other thread + Intent intent = new Intent(mActivity, KeychainIntentService.class); + + intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING); + + // fill values for this action + CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId); + parcel.mCertifyActions.addAll(certifyActions); + + Bundle data = new Bundle(); + data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after signing is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity, + getString(R.string.progress_certifying), ProgressDialog.STYLE_SPINNER, true) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + + Bundle data = message.getData(); + CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); + + Intent intent = new Intent(); + intent.putExtra(CertifyResult.EXTRA_RESULT, result); + mActivity.setResult(CertifyKeyActivity.RESULT_OK, intent); + + // check if we need to send the key to the server or not + if (mUploadKeyCheckbox.isChecked()) { + // upload the newly signed key to the keyserver + // TODO implement + // uploadKey(); + } else { + mActivity.finish(); + } + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(mActivity); + + // start service with intent + mActivity.startService(intent); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeActivity.java index 6193be297..cc66a33a3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeActivity.java @@ -17,10 +17,12 @@ package org.sufficientlysecure.keychain.ui; +import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.view.View; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.ImageView; import org.sufficientlysecure.keychain.Constants; @@ -38,8 +40,6 @@ public class QrCodeActivity extends ActionBarActivity { private ImageView mFingerprintQrCode; - private static final int QR_CODE_SIZE = 1000; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -87,7 +87,20 @@ public class QrCodeActivity extends ActionBarActivity { String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; - mFingerprintQrCode.setImageBitmap(QrCodeUtils.getQRCodeBitmap(qrCodeContent, QR_CODE_SIZE)); + + // create a minimal size qr code, we can keep this in ram no problem + final Bitmap qrCode = QrCodeUtils.getQRCodeBitmap(qrCodeContent, 0); + + mFingerprintQrCode.getViewTreeObserver().addOnGlobalLayoutListener( + new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // create actual bitmap in display dimensions + Bitmap scaled = Bitmap.createScaledBitmap(qrCode, + mFingerprintQrCode.getWidth(), mFingerprintQrCode.getWidth(), false); + mFingerprintQrCode.setImageBitmap(scaled); + } + }); } catch (ProviderHelper.NotFoundException e) { Log.e(Constants.TAG, "key not found!", e); Notify.showNotify(this, R.string.error_key_not_found, Style.ERROR); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java index 9343b166a..621b6b7e1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java @@ -42,9 +42,11 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; +import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import java.util.Vector; @@ -257,9 +259,10 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.USER_ID, - KeyRings.EXPIRY, + KeyRings.IS_EXPIRED, KeyRings.IS_REVOKED, KeyRings.HAS_ENCRYPT, + KeyRings.VERIFIED, }; String inMasterKeyList = null; @@ -344,7 +347,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T private class SelectPublicKeyCursorAdapter extends SelectKeyCursorAdapter { - private int mIndexHasEncrypt; + private int mIndexHasEncrypt, mIndexIsVerified; public SelectPublicKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) { super(context, c, flags, listView); @@ -355,6 +358,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T super.initIndex(cursor); if (cursor != null) { mIndexHasEncrypt = cursor.getColumnIndexOrThrow(KeyRings.HAS_ENCRYPT); + mIndexIsVerified = cursor.getColumnIndexOrThrow(KeyRings.VERIFIED); } } @@ -369,12 +373,18 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T h.selected.setChecked(getListView().isItemChecked(cursor.getPosition())); boolean enabled = false; - if((Boolean) h.status.getTag()) { + if((Boolean) h.statusIcon.getTag()) { // Check if key is viable for our purposes if (cursor.getInt(mIndexHasEncrypt) == 0) { - h.status.setText(R.string.no_key); + h.statusIcon.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, KeyFormattingUtils.STATE_UNAVAILABLE); + enabled = false; + } else if (cursor.getInt(mIndexIsVerified) != 0) { + h.statusIcon.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, KeyFormattingUtils.STATE_VERIFIED); + enabled = true; } else { - h.status.setText(R.string.can_encrypt); + h.statusIcon.setVisibility(View.GONE); enabled = true; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java index 0f9a52be6..1efd2c935 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java @@ -21,11 +21,6 @@ import android.annotation.TargetApi; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.TransitionDrawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; @@ -37,6 +32,7 @@ import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.AlphaAnimation; import android.widget.ImageView; import android.widget.TextView; @@ -404,21 +400,25 @@ public class ViewKeyShareFragment extends LoaderFragment implements new AsyncTask<Void, Void, Bitmap>() { protected Bitmap doInBackground(Void... unused) { String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; - return QrCodeUtils.getQRCodeBitmap(qrCodeContent, QR_CODE_SIZE); + // render with minimal size + return QrCodeUtils.getQRCodeBitmap(qrCodeContent, 0); } protected void onPostExecute(Bitmap qrCode) { // only change view, if fragment is attached to activity if (ViewKeyShareFragment.this.isAdded()) { - // Transition drawable with a transparent drawable and the final bitmap - final TransitionDrawable td = - new TransitionDrawable(new Drawable[]{ - new ColorDrawable(Color.TRANSPARENT), - new BitmapDrawable(getResources(), qrCode) - }); - - mFingerprintQrCode.setImageDrawable(td); - td.startTransition(200); + + // scale the image up to our actual size. we do this in code rather + // than let the ImageView do this because we don't require filtering. + Bitmap scaled = Bitmap.createScaledBitmap(qrCode, + mFingerprintQrCode.getHeight(), mFingerprintQrCode.getHeight(), + false); + mFingerprintQrCode.setImageBitmap(scaled); + + // simple fade-in animation + AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); + anim.setDuration(200); + mFingerprintQrCode.startAnimation(anim); } } }; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/AsyncTaskResultWrapper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/AsyncTaskResultWrapper.java index 5f2aec4fe..dc0c5846a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/AsyncTaskResultWrapper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/AsyncTaskResultWrapper.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain.ui.adapter; +import org.sufficientlysecure.keychain.service.results.OperationResult; + /** * The AsyncTaskResultWrapper is used to wrap a result from a AsyncTask (for example: Loader). * You can pass the result and an exception in it if an error occurred. @@ -28,19 +30,19 @@ package org.sufficientlysecure.keychain.ui.adapter; public class AsyncTaskResultWrapper<T> { private final T mResult; - private final Exception mError; + private final OperationResult mOperationResult; - public AsyncTaskResultWrapper(T result, Exception error) { + public AsyncTaskResultWrapper(T result, OperationResult operationResult) { this.mResult = result; - this.mError = error; + this.mOperationResult = operationResult; } public T getResult() { return mResult; } - public Exception getError() { - return mError; + public OperationResult getOperationResult() { + return mOperationResult; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java index 0332e8882..cb51a15a9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java @@ -21,6 +21,9 @@ import android.content.Context; import android.support.v4.content.AsyncTaskLoader; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.keyimport.Keyserver; +import org.sufficientlysecure.keychain.service.results.GetKeyResult; +import org.sufficientlysecure.keychain.service.results.OperationResult; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.keyimport.CloudSearch; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; @@ -93,7 +96,8 @@ public class ImportKeysListCloudLoader */ private void queryServer(boolean enforceFingerprint) { try { - ArrayList<ImportKeysListEntry> searchResult = CloudSearch.search(mServerQuery, mCloudPrefs); + ArrayList<ImportKeysListEntry> searchResult + = CloudSearch.search(mServerQuery, mCloudPrefs); mEntryList.clear(); // add result to data @@ -114,9 +118,29 @@ public class ImportKeysListCloudLoader } else { mEntryList.addAll(searchResult); } - mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null); - } catch (Exception e) { - mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e); + GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_OK, null); + mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, getKeyResult); + } catch (Keyserver.CloudSearchFailureException e) { + // convert exception to result parcel + int error = GetKeyResult.RESULT_ERROR; + OperationResult.LogType logType = null; + if (e instanceof Keyserver.QueryFailedException) { + error = GetKeyResult.RESULT_ERROR_QUERY_FAILED; + logType = OperationResult.LogType.MSG_GET_QUERY_FAILED; + } else if (e instanceof Keyserver.TooManyResponsesException) { + error = GetKeyResult.RESULT_ERROR_TOO_MANY_RESPONSES; + logType = OperationResult.LogType.MSG_GET_TOO_MANY_RESPONSES; + } else if (e instanceof Keyserver.QueryTooShortException) { + error = GetKeyResult.RESULT_ERROR_QUERY_TOO_SHORT; + logType = OperationResult.LogType.MSG_GET_QUERY_TOO_SHORT; + } else if (e instanceof Keyserver.QueryTooShortOrTooManyResponsesException) { + error = GetKeyResult.RESULT_ERROR_TOO_SHORT_OR_TOO_MANY_RESPONSES; + logType = OperationResult.LogType.MSG_GET_QUERY_TOO_SHORT_OR_TOO_MANY_RESPONSES; + } + OperationResult.OperationLog log = new OperationResult.OperationLog(); + log.add(logType, 0); + GetKeyResult getKeyResult = new GetKeyResult(error, log); + mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, getKeyResult); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java index 04947da93..6664cb61a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java @@ -25,6 +25,8 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.service.results.GetKeyResult; +import org.sufficientlysecure.keychain.service.results.OperationResult; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PositionAwareInputStream; @@ -37,9 +39,6 @@ import java.util.Iterator; public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { - public static class NoValidKeysException extends Exception { - } - public static class NonPgpPartException extends Exception { private int mCount; @@ -72,7 +71,8 @@ public class ImportKeysListLoader return mEntryListWrapper; } - mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mData, null); + GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_OK, null); + mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mData, getKeyResult); if (mInputData == null) { Log.e(Constants.TAG, "Input data is null!"); @@ -136,13 +136,11 @@ public class ImportKeysListLoader } } catch (IOException e) { Log.e(Constants.TAG, "IOException on parsing key file! Return NoValidKeysException!", e); - - NoValidKeysException e1 = new NoValidKeysException(); + OperationResult.OperationLog log = new OperationResult.OperationLog(); + log.add(OperationResult.LogType.MSG_GET_NO_VALID_KEYS, 0); + GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_NO_VALID_KEYS, log); mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> - (mData, e1); - } catch (Exception e) { - Log.e(Constants.TAG, "Other Exception on parsing key file!", e); - mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mData, e); + (mData, getKeyResult); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java new file mode 100644 index 000000000..2c0bf0842 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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.ui.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.os.Parcel; +import android.support.v4.util.LongSparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.support.v4.widget.CursorAdapter; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; + +public class MultiUserIdsAdapter extends CursorAdapter { + private LayoutInflater mInflater; + private final ArrayList<Boolean> mCheckStates; + + public MultiUserIdsAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + mInflater = LayoutInflater.from(context); + mCheckStates = new ArrayList<Boolean>(); + } + + @Override + public Cursor swapCursor(Cursor newCursor) { + mCheckStates.clear(); + if (newCursor != null) { + int count = newCursor.getCount(); + mCheckStates.ensureCapacity(count); + // initialize to true (use case knowledge: we usually want to sign all uids) + for (int i = 0; i < count; i++) { + mCheckStates.add(true); + } + } + + return super.swapCursor(newCursor); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.multi_certify_item, null); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + View vHeader = view.findViewById(R.id.user_id_header); + TextView vHeaderId = (TextView) view.findViewById(R.id.user_id_header_id); + TextView vName = (TextView) view.findViewById(R.id.user_id_item_name); + TextView vAddresses = (TextView) view.findViewById(R.id.user_id_item_addresses); + + byte[] data = cursor.getBlob(1); + int isHeader = cursor.getInt(2); + Parcel p = Parcel.obtain(); + p.unmarshall(data, 0, data.length); + p.setDataPosition(0); + ArrayList<String> uids = p.createStringArrayList(); + p.recycle(); + + if (isHeader == 1) { + long masterKeyId = cursor.getLong(0); + vHeader.setVisibility(View.VISIBLE); + vHeaderId.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(mContext, masterKeyId)); + } else { + vHeader.setVisibility(View.GONE); + } + + { // first one + String userId = uids.get(0); + String[] splitUserId = KeyRing.splitUserId(userId); + if (splitUserId[0] != null) { + vName.setText(splitUserId[0]); + } else { + vName.setText(R.string.user_id_no_name); + } + } + + StringBuilder lines = new StringBuilder(); + for (String uid : uids) { + String[] splitUserId = KeyRing.splitUserId(uid); + if (splitUserId[1] == null) { + continue; + } + lines.append(splitUserId[1]); + if (splitUserId[2] != null) { + lines.append(" (").append(splitUserId[2]).append(")"); + } + lines.append('\n'); + } + + // If we have any data here, show it + if (lines.length() > 0) { + // delete last newline + lines.setLength(lines.length() - 1); + vAddresses.setVisibility(View.VISIBLE); + vAddresses.setText(lines); + } else { + vAddresses.setVisibility(View.GONE); + } + + final CheckBox vCheckBox = (CheckBox) view.findViewById(R.id.user_id_item_check_box); + final int position = cursor.getPosition(); + vCheckBox.setOnCheckedChangeListener(null); + vCheckBox.setChecked(mCheckStates.get(position)); + vCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + mCheckStates.set(position, b); + } + }); + vCheckBox.setClickable(false); + + View vUidBody = view.findViewById(R.id.user_id_body); + vUidBody.setClickable(true); + vUidBody.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + vCheckBox.toggle(); + } + }); + + } + + public ArrayList<CertifyAction> getSelectedCertifyActions() { + LongSparseArray<CertifyAction> actions = new LongSparseArray<CertifyAction>(); + for (int i = 0; i < mCheckStates.size(); i++) { + if (mCheckStates.get(i)) { + mCursor.moveToPosition(i); + + long keyId = mCursor.getLong(0); + byte[] data = mCursor.getBlob(1); + + Parcel p = Parcel.obtain(); + p.unmarshall(data, 0, data.length); + p.setDataPosition(0); + ArrayList<String> uids = p.createStringArrayList(); + p.recycle(); + + CertifyAction action = actions.get(keyId); + if (actions.get(keyId) == null) { + actions.put(keyId, new CertifyAction(keyId, uids)); + } else { + action.mUserIds.addAll(uids); + } + } + } + + ArrayList<CertifyAction> result = new ArrayList<CertifyAction>(actions.size()); + for (int i = 0; i < actions.size(); i++) { + result.add(actions.valueAt(i)); + } + return result; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index 6947fc1ff..c864c7138 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -24,6 +24,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; +import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; @@ -33,8 +34,6 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.util.Highlighter; -import java.util.Date; - /** * Yes this class is abstract! @@ -44,7 +43,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { private String mQuery; private LayoutInflater mInflater; - protected int mIndexUserId, mIndexMasterKeyId, mIndexRevoked, mIndexExpiry; + protected int mIndexUserId, mIndexMasterKeyId, mIndexIsExpiry, mIndexIsRevoked; public SelectKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) { super(context, c, flags); @@ -73,8 +72,8 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { if (cursor != null) { mIndexUserId = cursor.getColumnIndexOrThrow(KeyRings.USER_ID); mIndexMasterKeyId = cursor.getColumnIndexOrThrow(KeyRings.MASTER_KEY_ID); - mIndexExpiry = cursor.getColumnIndexOrThrow(KeyRings.EXPIRY); - mIndexRevoked = cursor.getColumnIndexOrThrow(KeyRings.IS_REVOKED); + mIndexIsExpiry = cursor.getColumnIndexOrThrow(KeyRings.IS_EXPIRED); + mIndexIsRevoked = cursor.getColumnIndexOrThrow(KeyRings.IS_REVOKED); } } @@ -90,7 +89,8 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { public static class ViewHolderItem { public View view; - public TextView mainUserId, mainUserIdRest, keyId, status; + public TextView mainUserId, mainUserIdRest, keyId; + public ImageView statusIcon; public CheckBox selected; public void setEnabled(boolean enabled) { @@ -99,7 +99,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { mainUserId.setEnabled(enabled); mainUserIdRest.setEnabled(enabled); keyId.setEnabled(enabled); - status.setEnabled(enabled); + statusIcon.setEnabled(enabled); // Sorta special: We set an item as clickable to disable it in the ListView. This works // because the list item will handle the clicks itself (which is a nop) @@ -130,19 +130,21 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { long masterKeyId = cursor.getLong(mIndexMasterKeyId); h.keyId.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(mContext, masterKeyId)); - boolean enabled = true; - if (cursor.getInt(mIndexRevoked) != 0) { - h.status.setText(R.string.revoked); + boolean enabled; + if (cursor.getInt(mIndexIsRevoked) != 0) { + h.statusIcon.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, KeyFormattingUtils.STATE_REVOKED); enabled = false; - } else if (!cursor.isNull(mIndexExpiry) - && new Date(cursor.getLong(mIndexExpiry) * 1000).before(new Date())) { - h.status.setText(R.string.expired); + } else if (cursor.getInt(mIndexIsExpiry) != 0) { + h.statusIcon.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, KeyFormattingUtils.STATE_EXPIRED); enabled = false; } else { - h.status.setText(""); + h.statusIcon.setVisibility(View.GONE); + enabled = true; } - h.status.setTag(enabled); + h.statusIcon.setTag(enabled); } @Override @@ -153,7 +155,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { holder.mainUserId = (TextView) view.findViewById(R.id.mainUserId); holder.mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); holder.keyId = (TextView) view.findViewById(R.id.subkey_item_key_id); - holder.status = (TextView) view.findViewById(R.id.status); + holder.statusIcon = (ImageView) view.findViewById(R.id.status_icon); holder.selected = (CheckBox) view.findViewById(R.id.selected); view.setTag(holder); return view; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/UserIdInfoDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/UserIdInfoDialogFragment.java index 968b2429b..57b171eb9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/UserIdInfoDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/UserIdInfoDialogFragment.java @@ -64,12 +64,12 @@ public class UserIdInfoDialogFragment extends DialogFragment { } else { switch (isVerified) { case KeychainContract.Certs.VERIFIED_SECRET: - title = getString(R.string.user_id_info_verified_title); - message = getString(R.string.user_id_info_verified_text); + title = getString(R.string.user_id_info_certified_title); + message = getString(R.string.user_id_info_certified_text); break; case KeychainContract.Certs.VERIFIED_SELF: - title = getString(R.string.user_id_info_not_verified_title); - message = getString(R.string.user_id_info_not_verified_text); + title = getString(R.string.user_id_info_uncertified_title); + message = getString(R.string.user_id_info_uncertified_text); break; default: title = getString(R.string.user_id_info_invalid_title); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index 5c2bc76d0..afc3247be 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -19,18 +19,13 @@ package org.sufficientlysecure.keychain.ui.util; import android.content.Context; -import android.database.Cursor; import android.graphics.Color; import android.graphics.PorterDuff; -import android.graphics.Typeface; import android.text.Spannable; -import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; -import android.text.style.TypefaceSpan; -import android.view.View; import android.widget.ImageView; +import android.widget.TextView; import org.spongycastle.asn1.ASN1ObjectIdentifier; import org.spongycastle.asn1.nist.NISTNamedCurves; @@ -291,7 +286,7 @@ public class KeyFormattingUtils { } public static String beautifyKeyIdWithPrefix(Context context, String idHex) { - return "ID: " + beautifyKeyId(idHex); + return "Key ID: " + beautifyKeyId(idHex); } public static String beautifyKeyIdWithPrefix(Context context, long keyId) { @@ -386,36 +381,126 @@ public class KeyFormattingUtils { public static final int STATE_EXPIRED = 2; public static final int STATE_VERIFIED = 3; public static final int STATE_UNAVAILABLE = 4; + public static final int STATE_ENCRYPTED = 5; + public static final int STATE_NOT_ENCRYPTED = 6; + public static final int STATE_UNVERIFIED = 7; + public static final int STATE_UNKNOWN_KEY = 8; + public static final int STATE_INVALID = 9; + public static final int STATE_NOT_SIGNED = 10; + + public static void setStatusImage(Context context, ImageView statusIcon, int state) { + setStatusImage(context, statusIcon, null, state); + } /** * Sets status image based on constant */ - public static void setStatusImage(Context context, ImageView statusView, int state) { + public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText, int state) { switch (state) { - case STATE_REVOKED: - statusView.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout)); - statusView.setColorFilter(context.getResources().getColor(R.color.android_red_dark), + /** GREEN: everything is good **/ + case STATE_VERIFIED: { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_verified_cutout)); + statusIcon.setColorFilter(context.getResources().getColor(R.color.android_green_dark), + PorterDuff.Mode.SRC_ATOP); + if (statusText != null) { + statusText.setTextColor(context.getResources().getColor(R.color.android_green_dark)); + } + break; + } + case STATE_ENCRYPTED: { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_lock_closed)); + statusIcon.setColorFilter(context.getResources().getColor(R.color.android_green_dark), PorterDuff.Mode.SRC_ATOP); + if (statusText != null) { + statusText.setTextColor(context.getResources().getColor(R.color.android_green_dark)); + } break; - case STATE_EXPIRED: - statusView.setImageDrawable( + } + /** ORANGE: mostly bad... **/ + case STATE_UNVERIFIED: { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout)); + statusIcon.setColorFilter(context.getResources().getColor(R.color.android_orange_dark), + PorterDuff.Mode.SRC_ATOP); + if (statusText != null) { + statusText.setTextColor(context.getResources().getColor(R.color.android_orange_dark)); + } + break; + } + case STATE_EXPIRED: { + statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_expired_cutout)); - statusView.setColorFilter(context.getResources().getColor(R.color.android_orange_dark), + statusIcon.setColorFilter(context.getResources().getColor(R.color.android_orange_dark), + PorterDuff.Mode.SRC_ATOP); + if (statusText != null) { + statusText.setTextColor(context.getResources().getColor(R.color.android_orange_dark)); + } + break; + } + case STATE_UNKNOWN_KEY: { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout)); + statusIcon.setColorFilter(context.getResources().getColor(R.color.android_orange_dark), + PorterDuff.Mode.SRC_ATOP); + if (statusText != null) { + statusText.setTextColor(context.getResources().getColor(R.color.android_orange_dark)); + } + break; + } + /** RED: really bad... **/ + case STATE_REVOKED: { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout)); + statusIcon.setColorFilter(context.getResources().getColor(R.color.android_red_dark), + PorterDuff.Mode.SRC_ATOP); + if (statusText != null) { + statusText.setTextColor(context.getResources().getColor(R.color.android_red_dark)); + } + break; + } + case STATE_NOT_ENCRYPTED: { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_lock_open)); + statusIcon.setColorFilter(context.getResources().getColor(R.color.android_red_dark), PorterDuff.Mode.SRC_ATOP); + if (statusText != null) { + statusText.setTextColor(context.getResources().getColor(R.color.android_red_dark)); + } break; - case STATE_UNAVAILABLE: - statusView.setImageDrawable( + } + case STATE_NOT_SIGNED: { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout)); + statusIcon.setColorFilter(context.getResources().getColor(R.color.android_red_dark), + PorterDuff.Mode.SRC_ATOP); + if (statusText != null) { + statusText.setTextColor(context.getResources().getColor(R.color.android_red_dark)); + } + break; + } + case STATE_INVALID: { + statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout)); - statusView.setColorFilter(context.getResources().getColor(R.color.bg_gray), + statusIcon.setColorFilter(context.getResources().getColor(R.color.android_red_dark), PorterDuff.Mode.SRC_ATOP); + if (statusText != null) { + statusText.setTextColor(context.getResources().getColor(R.color.android_red_dark)); + } break; - case STATE_VERIFIED: - statusView.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_verified_cutout)); - statusView.setColorFilter(context.getResources().getColor(R.color.android_green_dark), + } + /** special **/ + case STATE_UNAVAILABLE: { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout)); + statusIcon.setColorFilter(context.getResources().getColor(R.color.bg_gray), PorterDuff.Mode.SRC_ATOP); + if (statusText != null) { + statusText.setTextColor(context.getResources().getColor(R.color.bg_gray)); + } break; + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java index dd07a16b0..36f38045f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java @@ -37,7 +37,6 @@ import java.util.Hashtable; * Copied from Bitcoin Wallet */ public class QrCodeUtils { - public static final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter(); /** * Generate Bitmap with QR Code based on input. @@ -50,7 +49,7 @@ public class QrCodeUtils { try { final Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>(); hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); - final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, + final BitMatrix result = new QRCodeWriter().encode(input, BarcodeFormat.QR_CODE, size, size, hints); final int width = result.getWidth(); @@ -60,7 +59,7 @@ public class QrCodeUtils { for (int y = 0; y < height; y++) { final int offset = y * width; for (int x = 0; x < width; x++) { - pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT; + pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE; } } 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 07f6529f1..0a1a1d75b 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 @@ -84,13 +84,19 @@ public class CertifyKeySpinner extends KeySpinner { @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { super.onLoadFinished(loader, data); - // If there is only one choice, pick it by default - if (mAdapter.getCount() == 2) { - setSelection(1); + + if (loader.getId() == LOADER_ID) { + // If there is only one choice, pick it by default + if (mAdapter.getCount() == 2) { + // preselect if key can certify + if (data.moveToPosition(1) && data.isNull(mIndexHasCertify)) { + setSelection(1); + } + } + mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY); + mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED); + mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED); } - mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY); - mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED); - mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ExchangeKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ExchangeKeySpinner.java new file mode 100644 index 000000000..e31d14d48 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ExchangeKeySpinner.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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.ui.widget; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.util.AttributeSet; +import android.widget.ImageView; + +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + +public class ExchangeKeySpinner extends KeySpinner { + public ExchangeKeySpinner(Context context) { + super(context); + } + + public ExchangeKeySpinner(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ExchangeKeySpinner(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public Loader<Cursor> onCreateLoader(int loaderId, Bundle data) { + // This is called when a new Loader needs to be created. This + // sample only has one Loader, so we don't care about the ID. + Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri(); + + // These are the rows that we will retrieve. + String[] projection = new String[]{ + KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.KEY_ID, + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.IS_REVOKED, + KeychainContract.KeyRings.IS_EXPIRED, + KeychainContract.KeyRings.HAS_ANY_SECRET + }; + + String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1"; + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getContext(), baseUri, projection, where, null, null); + } + + private int mIndexIsRevoked, mIndexIsExpired; + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + super.onLoadFinished(loader, data); + + if (loader.getId() == LOADER_ID) { + // If there is only one choice, pick it by default + if (mAdapter.getCount() == 2) { + setSelection(1); + } + mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED); + mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED); + } + } + + @Override + boolean setStatus(Context context, Cursor cursor, ImageView statusView) { + if (cursor.getInt(mIndexIsRevoked) != 0) { + KeyFormattingUtils.setStatusImage(getContext(), statusView, KeyFormattingUtils.STATE_REVOKED); + return false; + } + if (cursor.getInt(mIndexIsExpired) != 0) { + KeyFormattingUtils.setStatusImage(getContext(), statusView, KeyFormattingUtils.STATE_EXPIRED); + return false; + } + + // valid key + return true; + } + +} 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 bb4ae3ab6..7ec6ffe95 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 @@ -50,6 +50,9 @@ public abstract class KeySpinner extends Spinner implements LoaderManager.Loader protected SelectKeyAdapter mAdapter = new SelectKeyAdapter(); protected OnKeyChangedListener mListener; + // this shall note collide with other loaders inside the activity + protected int LOADER_ID = 2343; + public KeySpinner(Context context) { super(context); initView(); @@ -101,7 +104,7 @@ public abstract class KeySpinner extends Spinner implements LoaderManager.Loader public void reload() { if (getContext() instanceof FragmentActivity) { - ((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(0, null, this); + ((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this); } else { Log.e(Constants.TAG, "KeySpinner must be attached to FragmentActivity, this is " + getContext().getClass()); } @@ -109,12 +112,16 @@ public abstract class KeySpinner extends Spinner implements LoaderManager.Loader @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - mAdapter.swapCursor(data); + if (loader.getId() == LOADER_ID) { + mAdapter.swapCursor(data); + } } @Override public void onLoaderReset(Loader<Cursor> loader) { - mAdapter.swapCursor(null); + if (loader.getId() == LOADER_ID) { + mAdapter.swapCursor(null); + } } public long getSelectedKeyId() { 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 ce1f7bb44..2f002d470 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 @@ -73,9 +73,12 @@ public class SignKeySpinner extends KeySpinner { @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { super.onLoadFinished(loader, data); - mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN); - mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED); - mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED); + + if (loader.getId() == LOADER_ID) { + mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN); + mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED); + mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED); + } } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java index 357a9603c..afa2e75fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java @@ -59,7 +59,6 @@ public class KeyUpdateHelper { for (String fprint : providerHelper.getAllFingerprints(KeychainContract.KeyRings.buildUnifiedKeyRingsUri())) { ImportKeysListEntry key = new ImportKeysListEntry(); key.setFingerprintHex(fprint); - key.setBitStrength(1337); key.addOrigin(servers[0]); keys.add(key); } diff --git a/OpenKeychain/src/main/res/drawable-hdpi/encrypted_small.png b/OpenKeychain/src/main/res/drawable-hdpi/encrypted_small.png Binary files differdeleted file mode 100644 index 3ff8e9b97..000000000 --- a/OpenKeychain/src/main/res/drawable-hdpi/encrypted_small.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cloud.png Binary files differnew file mode 100644 index 000000000..3daa64131 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cloud.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_search_cloud.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_search_cloud.png Binary files differnew file mode 100644 index 000000000..ba7236da3 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_search_cloud.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/overlay_error.png b/OpenKeychain/src/main/res/drawable-hdpi/overlay_error.png Binary files differdeleted file mode 100644 index e6d7e60ba..000000000 --- a/OpenKeychain/src/main/res/drawable-hdpi/overlay_error.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-hdpi/overlay_ok.png b/OpenKeychain/src/main/res/drawable-hdpi/overlay_ok.png Binary files differdeleted file mode 100644 index 0672f869d..000000000 --- a/OpenKeychain/src/main/res/drawable-hdpi/overlay_ok.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-hdpi/revoked_key_small.png b/OpenKeychain/src/main/res/drawable-hdpi/revoked_key_small.png Binary files differdeleted file mode 100644 index 75f45eb54..000000000 --- a/OpenKeychain/src/main/res/drawable-hdpi/revoked_key_small.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-hdpi/signed_large.png b/OpenKeychain/src/main/res/drawable-hdpi/signed_large.png Binary files differdeleted file mode 100644 index c209f4167..000000000 --- a/OpenKeychain/src/main/res/drawable-hdpi/signed_large.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-ldpi/overlay_error.png b/OpenKeychain/src/main/res/drawable-ldpi/overlay_error.png Binary files differdeleted file mode 100644 index e5a88e18f..000000000 --- a/OpenKeychain/src/main/res/drawable-ldpi/overlay_error.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-ldpi/overlay_ok.png b/OpenKeychain/src/main/res/drawable-ldpi/overlay_ok.png Binary files differdeleted file mode 100644 index 63374d47f..000000000 --- a/OpenKeychain/src/main/res/drawable-ldpi/overlay_ok.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-ldpi/signed_large.png b/OpenKeychain/src/main/res/drawable-ldpi/signed_large.png Binary files differdeleted file mode 100644 index d2917644c..000000000 --- a/OpenKeychain/src/main/res/drawable-ldpi/signed_large.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cloud.png Binary files differnew file mode 100644 index 000000000..266d4c21f --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cloud.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_search_cloud.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_search_cloud.png Binary files differnew file mode 100644 index 000000000..e1067f73c --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_search_cloud.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/overlay_error.png b/OpenKeychain/src/main/res/drawable-mdpi/overlay_error.png Binary files differdeleted file mode 100644 index 5fe017433..000000000 --- a/OpenKeychain/src/main/res/drawable-mdpi/overlay_error.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-mdpi/overlay_ok.png b/OpenKeychain/src/main/res/drawable-mdpi/overlay_ok.png Binary files differdeleted file mode 100644 index b4f332260..000000000 --- a/OpenKeychain/src/main/res/drawable-mdpi/overlay_ok.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-mdpi/signed_large.png b/OpenKeychain/src/main/res/drawable-mdpi/signed_large.png Binary files differdeleted file mode 100644 index ab9495e7b..000000000 --- a/OpenKeychain/src/main/res/drawable-mdpi/signed_large.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-mdpi/signed_small.png b/OpenKeychain/src/main/res/drawable-mdpi/signed_small.png Binary files differdeleted file mode 100644 index 4202c3f97..000000000 --- a/OpenKeychain/src/main/res/drawable-mdpi/signed_small.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cloud.png Binary files differnew file mode 100644 index 000000000..0769899fd --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cloud.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_search_cloud.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_search_cloud.png Binary files differnew file mode 100644 index 000000000..b81772f20 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_search_cloud.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_cloud.png Binary files differnew file mode 100644 index 000000000..f97084dbe --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_cloud.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_search_cloud.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_search_cloud.png Binary files differnew file mode 100644 index 000000000..7c8b36bc0 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_search_cloud.png diff --git a/OpenKeychain/src/main/res/drawable/overlay_error.png b/OpenKeychain/src/main/res/drawable/overlay_error.png Binary files differdeleted file mode 100644 index 2372de59e..000000000 --- a/OpenKeychain/src/main/res/drawable/overlay_error.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable/overlay_ok.png b/OpenKeychain/src/main/res/drawable/overlay_ok.png Binary files differdeleted file mode 100644 index 2f0005898..000000000 --- a/OpenKeychain/src/main/res/drawable/overlay_ok.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/drawable/signed_large.png b/OpenKeychain/src/main/res/drawable/signed_large.png Binary files differdeleted file mode 100644 index 92e64dc51..000000000 --- a/OpenKeychain/src/main/res/drawable/signed_large.png +++ /dev/null diff --git a/OpenKeychain/src/main/res/layout/add_keys_activity.xml b/OpenKeychain/src/main/res/layout/add_keys_activity.xml new file mode 100644 index 000000000..c703efbf3 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/add_keys_activity.xml @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:id="@+id/content_frame" + android:layout_marginLeft="@dimen/drawer_content_padding" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <include layout="@layout/notify_area" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="4dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:orientation="vertical"> + + <TextView + style="@style/SectionHeader" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="@string/add_keys_section_secure_exchange" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:paddingLeft="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/add_keys_my_key" + android:paddingRight="8dp" /> + + <org.sufficientlysecure.keychain.ui.widget.ExchangeKeySpinner + android:id="@+id/add_keys_safeslinger_key_spinner" + android:minHeight="56dip" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/add_keys_safeslinger" + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:clickable="true" + android:paddingRight="4dp" + style="@style/SelectableItem" + android:orientation="horizontal"> + + <TextView + android:paddingLeft="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="0dip" + android:layout_height="match_parent" + android:text="@string/add_keys_start_exchange" + android:layout_weight="1" + android:gravity="center_vertical" /> + + <ImageView + android:id="@+id/add_keys_safeslinger_icon" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:padding="8dp" + android:src="@drawable/ic_action_safeslinger" + android:layout_gravity="center_vertical" /> + + </LinearLayout> + + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" + android:layout_marginBottom="8dp" /> + + <TextView + style="@style/SectionHeader" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="32dp" + android:text="@string/add_keys_section_secure_add" /> + + <TextView + android:id="@+id/add_keys_qr_code" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:clickable="true" + style="@style/SelectableItem" + android:text="@string/add_keys_qr_code" + android:drawableRight="@drawable/ic_action_qr_code" + android:drawablePadding="8dp" + android:gravity="center_vertical" /> + + <View + android:id="@+id/view_key_action_certify_divider" + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/add_keys_nfc" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:clickable="true" + style="@style/SelectableItem" + android:text="@string/add_keys_nfc" + android:drawableRight="@drawable/ic_action_nfc" + android:drawablePadding="8dp" + android:gravity="center_vertical" /> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" + android:layout_marginBottom="8dp" /> + + </LinearLayout> + + </LinearLayout> + +</ScrollView>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/create_key_input_fragment.xml b/OpenKeychain/src/main/res/layout/create_key_input_fragment.xml index 449c9fdf0..d92988111 100644 --- a/OpenKeychain/src/main/res/layout/create_key_input_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_key_input_fragment.xml @@ -135,7 +135,7 @@ android:layout_weight="1" android:text="@string/btn_next" android:minHeight="?android:attr/listPreferredItemHeight" - android:drawableRight="@drawable/ic_action_new_account" + android:drawableRight="@drawable/ic_action_play" android:drawablePadding="8dp" android:gravity="center_vertical" android:clickable="true" diff --git a/OpenKeychain/src/main/res/layout/decrypt_result_include.xml b/OpenKeychain/src/main/res/layout/decrypt_result_include.xml index fcad91df3..8a0519872 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_result_include.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_result_include.xml @@ -1,97 +1,144 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/result" + android:id="@+id/result_main_layout" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:paddingTop="4dp" - android:paddingBottom="4dp" - android:background="@color/android_purple_light"> + android:background="@color/holo_gray_bright"> - <View + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/result_main_layout" + android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="1dip" - android:background="?android:attr/listDivider" - android:layout_marginTop="4dp" /> - - <TextView - android:id="@+id/result_text" - android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:text="result text" - android:textColor="@color/white" - android:layout_gravity="center_horizontal" - android:layout_marginTop="8dp" - android:layout_marginBottom="8dp" /> + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingTop="4dp" + android:paddingBottom="4dp"> - <View - android:layout_width="match_parent" - android:layout_height="1dip" - android:background="?android:attr/listDivider" /> + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> - <RelativeLayout - android:id="@+id/result_signature" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:clickable="true" - android:orientation="horizontal" - android:layout_marginBottom="8dp" - android:layout_marginTop="8dp"> + <ImageView + android:id="@+id/result_encryption_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/status_lock_open" + android:layout_gravity="center_vertical" /> + + <TextView + android:id="@+id/result_encryption_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="Not Encrypted (set in-code)" + android:layout_marginLeft="8dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" /> + </LinearLayout> - <RelativeLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:id="@+id/result_signature_image"> + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> <ImageView - android:id="@+id/ic_signature" + android:id="@+id/result_signature_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:src="@drawable/signed_large" /> + android:src="@drawable/status_signature_unverified_cutout" + android:layout_gravity="center_vertical" /> - <ImageView - android:id="@+id/ic_signature_status" + <TextView + android:id="@+id/result_signature_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:src="@drawable/overlay_error" /> - </RelativeLayout> - - <TextView - android:id="@+id/mainUserId" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="4dp" - android:layout_gravity="left" - android:text="@string/label_main_user_id" - android:textAppearance="?android:attr/textAppearanceMedium" - android:layout_toRightOf="@+id/result_signature_image" - android:textColor="@color/white" /> - - <TextView - android:id="@+id/mainUserIdRest" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="4dp" - android:layout_gravity="left" - android:text="Main User Id Rest" - android:textAppearance="?android:attr/textAppearanceSmall" - android:layout_below="@+id/mainUserId" - android:layout_toRightOf="@+id/result_signature_image" - android:textColor="@color/white" /> - - <Button - android:id="@+id/lookup_key" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/btn_lookup_key" - android:drawableRight="@drawable/ic_action_download" - android:layout_alignParentRight="true" - android:layout_centerVertical="true" - android:background="@drawable/button_edgy"/> - - </RelativeLayout> + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="Signed by (set in-code)" + android:layout_marginLeft="8dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" /> + </LinearLayout> + + <View + android:id="@+id/result_signature_divider1" + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginLeft="32dp" + android:background="?android:attr/listDivider" /> + + <LinearLayout + android:id="@+id/result_signature_layout" + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:clickable="true" + android:layout_marginLeft="32dp" + android:paddingRight="4dp" + style="@style/SelectableItem" + android:orientation="horizontal"> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:paddingRight="4dp" + android:gravity="center_vertical" + android:orientation="vertical"> + + <TextView + android:id="@+id/result_signature_name" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Alice (set in-code)" /> + + <TextView + android:id="@+id/result_signature_email" + android:textAppearance="?android:attr/textAppearanceSmall" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="@color/tertiary_text_light" + android:text="alice@example.com (set in-code)" + android:gravity="center_vertical" /> + + </LinearLayout> + + <View + android:layout_width="1dip" + android:layout_height="match_parent" + android:gravity="right" + android:layout_marginBottom="8dp" + android:layout_marginTop="8dp" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/result_signature_action" + android:paddingLeft="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:text="Show" + android:drawableRight="@drawable/ic_action_accounts" + android:drawablePadding="8dp" + android:gravity="center_vertical" + style="@style/SelectableItem" /> + + </LinearLayout> + + <View + android:id="@+id/result_signature_divider2" + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginLeft="32dp" + android:background="?android:attr/listDivider" /> + + </LinearLayout> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> </LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml b/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml index 3bd144fbf..c58e2d7e6 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml @@ -6,73 +6,109 @@ <include layout="@layout/decrypt_result_include" /> - <ScrollView - android:fillViewport="true" - android:paddingTop="8dp" + <LinearLayout + android:visibility="gone" + android:id="@+id/decrypt_text_valid" android:layout_width="match_parent" - android:scrollbars="vertical" - android:layout_height="0dp" - android:layout_weight="1"> + android:layout_height="match_parent" + android:orientation="vertical"> - <TextView - android:id="@+id/decrypt_text_plaintext" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:textAppearance="?android:attr/textAppearanceMedium" + <ScrollView + android:fillViewport="true" + android:paddingTop="8dp" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="top" - android:hint="" - android:textIsSelectable="true" /> + android:scrollbars="vertical" + android:layout_height="0dp" + android:layout_weight="1"> - </ScrollView> + <TextView + android:id="@+id/decrypt_text_plaintext" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="top" + android:hint="" + android:textIsSelectable="true" /> - <View - android:layout_width="match_parent" - android:layout_height="1dip" - android:layout_marginLeft="16dp" - android:layout_marginRight="16dp" - android:background="?android:attr/listDivider" /> + </ScrollView> - <LinearLayout - android:id="@+id/action_decrypt_share_plaintext" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="16dp" - android:layout_marginRight="16dp" - android:clickable="true" - style="@style/SelectableItem" - android:orientation="horizontal"> + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:background="?android:attr/listDivider" /> - <TextView - android:paddingLeft="8dp" - android:paddingRight="8dp" - android:textAppearance="?android:attr/textAppearanceMedium" - android:layout_width="0dp" + <LinearLayout + android:id="@+id/action_decrypt_share_plaintext" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeight" - android:text="@string/btn_add_share_decrypted_text" - android:drawableRight="@drawable/ic_action_share" - android:drawablePadding="8dp" - android:gravity="center_vertical" - android:layout_weight="1" /> + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:clickable="true" + style="@style/SelectableItem" + android:orientation="horizontal"> - <View - android:layout_width="1dip" - android:layout_height="match_parent" - android:gravity="right" - android:layout_marginBottom="8dp" - android:layout_marginTop="8dp" - android:background="?android:attr/listDivider" /> + <TextView + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:text="@string/btn_add_share_decrypted_text" + android:drawableRight="@drawable/ic_action_share" + android:drawablePadding="8dp" + android:gravity="center_vertical" + android:layout_weight="1" /> + + <View + android:layout_width="1dip" + android:layout_height="match_parent" + android:gravity="right" + android:layout_marginBottom="8dp" + android:layout_marginTop="8dp" + android:background="?android:attr/listDivider" /> + + <ImageButton + android:id="@+id/action_decrypt_copy_plaintext" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:padding="8dp" + android:src="@drawable/ic_action_copy" + android:layout_gravity="center_vertical" + style="@style/SelectableItem" /> + + </LinearLayout> - <ImageButton - android:id="@+id/action_decrypt_copy_plaintext" + </LinearLayout> + + <LinearLayout + android:visibility="gone" + android:id="@+id/decrypt_text_invalid" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center_vertical"> + + <TextView android:layout_width="wrap_content" - android:layout_height="match_parent" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/decrypt_invalid_text" android:padding="8dp" - android:src="@drawable/ic_action_copy" - android:layout_gravity="center_vertical" - style="@style/SelectableItem" /> + android:layout_gravity="center" + android:textColor="@color/android_red_dark" /> + <Button + android:id="@+id/decrypt_text_invalid_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/button_edgy" + android:textColor="@color/android_red_dark" + android:text="@string/decrypt_invalid_button" + android:layout_gravity="center_horizontal" /> </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/key_list_content.xml b/OpenKeychain/src/main/res/layout/key_list_content.xml index 66b009c78..8ab63610f 100644 --- a/OpenKeychain/src/main/res/layout/key_list_content.xml +++ b/OpenKeychain/src/main/res/layout/key_list_content.xml @@ -6,17 +6,42 @@ <include layout="@layout/notify_area" /> - <FrameLayout + <LinearLayout android:id="@+id/content_frame" android:layout_marginLeft="@dimen/drawer_content_padding" + android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <LinearLayout + android:orientation="vertical" + android:background="@color/holo_gray_bright" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Spinner + android:id="@+id/key_list_filter_spinner" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="12dp" + android:layout_marginRight="12dp" + android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" /> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> + + </LinearLayout> + <fragment android:id="@+id/key_list_fragment" android:name="org.sufficientlysecure.keychain.ui.KeyListFragment" android:layout_width="match_parent" - android:layout_height="match_parent" /> - </FrameLayout> + android:layout_height="0dp" + android:layout_weight="1" /> + + </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/key_list_item.xml b/OpenKeychain/src/main/res/layout/key_list_item.xml index a7a195ffa..bddc2ad97 100644 --- a/OpenKeychain/src/main/res/layout/key_list_item.xml +++ b/OpenKeychain/src/main/res/layout/key_list_item.xml @@ -39,7 +39,7 @@ </LinearLayout> <ImageView - android:id="@+id/status_image" + android:id="@+id/status_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" diff --git a/OpenKeychain/src/main/res/layout/keyspinner_item.xml b/OpenKeychain/src/main/res/layout/keyspinner_item.xml index 411ceefdf..b75bb808e 100644 --- a/OpenKeychain/src/main/res/layout/keyspinner_item.xml +++ b/OpenKeychain/src/main/res/layout/keyspinner_item.xml @@ -2,12 +2,12 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="24dp" android:gravity="center_vertical" android:singleLine="true" android:orientation="horizontal" android:descendantFocusability="blocksDescendants" - android:focusable="false"> + android:focusable="false" + android:minHeight="44dip"> <LinearLayout android:layout_width="0dip" @@ -17,9 +17,7 @@ android:focusable="true" android:orientation="vertical" android:paddingLeft="8dp" - android:paddingRight="4dp" - android:paddingTop="4dp" - android:paddingBottom="4dp"> + android:paddingRight="4dp"> <TextView android:id="@+id/keyspinner_key_name" @@ -44,7 +42,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="end" - android:textAppearance="?android:attr/textAppearanceSmall"/> + android:textAppearance="?android:attr/textAppearanceSmall" /> </LinearLayout> <ImageView @@ -53,6 +51,7 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/status_signature_revoked_cutout" - android:padding="16dp" /> + android:paddingLeft="16dp" + android:paddingRight="16dp" /> </LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/log_display_fragment.xml b/OpenKeychain/src/main/res/layout/log_display_fragment.xml deleted file mode 100644 index 442e72d09..000000000 --- a/OpenKeychain/src/main/res/layout/log_display_fragment.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="match_parent"> - - <ListView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/log_text" /> -</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/log_display_item.xml b/OpenKeychain/src/main/res/layout/log_display_item.xml index 35489afed..d3938aaf0 100644 --- a/OpenKeychain/src/main/res/layout/log_display_item.xml +++ b/OpenKeychain/src/main/res/layout/log_display_item.xml @@ -1,22 +1,70 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="horizontal" android:layout_width="match_parent" - android:layout_height="match_parent"> - - <ImageView - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:id="@+id/log_img" - android:minWidth="10dp" - android:background="@color/bg_gray" /> - - <TextView - android:layout_width="wrap_content" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:id="@+id/log_img" + android:minWidth="10dp" + android:background="@color/bg_gray" /> + + <TextView + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="Log Entry Text" + android:id="@+id/log_text" + android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" + android:layout_marginLeft="8dp" + android:layout_weight="1" + android:layout_gravity="center_vertical"/> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/log_sub" + android:src="@drawable/ic_action_view_as_list" + android:layout_marginBottom="4dp" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:gravity="center_vertical" + android:layout_marginTop="4dp" /> + + </LinearLayout> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Log Entry Text" - android:id="@+id/log_text" - android:layout_marginTop="4dp" - android:layout_marginBottom="4dp" - android:layout_marginLeft="8dp" /> + android:id="@+id/log_second" + android:layout_marginLeft="8dip"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:id="@+id/log_second_img" + android:minWidth="10dp" + android:background="@color/bg_gray" /> + + <TextView + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="Log Entry Text" + android:id="@+id/log_second_text" + android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" + android:layout_marginLeft="8dp" + android:layout_weight="1" + android:layout_gravity="center_vertical"/> + </LinearLayout> + </LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/multi_certify_item.xml b/OpenKeychain/src/main/res/layout/multi_certify_item.xml new file mode 100644 index 000000000..c578473d3 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/multi_certify_item.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:orientation="vertical" + android:singleLine="true"> + + <LinearLayout android:id="@+id/user_id_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:clickable="true" + android:layout_marginLeft="8dip" + android:layout_marginTop="16dip"> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:id="@+id/user_id_header_id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="ID: 0123 4567 890a bcde" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + </LinearLayout> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" + android:paddingLeft="24dip" + android:paddingRight="24dip" + android:layout_marginBottom="4dip"/> + + </LinearLayout> + + <LinearLayout android:id="@+id/user_id_body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:singleLine="true" + android:layout_marginLeft="8dip" + android:layout_marginTop="4dip"> + + <CheckBox + android:id="@+id/user_id_item_check_box" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:clickable="false" + android:focusable="false" /> + + <LinearLayout + android:orientation="vertical" + android:layout_gravity="center_vertical" + android:layout_width="0dip" + android:layout_marginLeft="8dp" + android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:minHeight="48dip"> + + <TextView + android:id="@+id/user_id_item_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="alice@example.com" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_weight="1" + android:gravity="center_vertical"/> + + <TextView + android:id="@+id/user_id_item_addresses" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="alice@example.com\na-lyc@example.com" + android:textAppearance="?android:attr/textAppearanceSmall" + android:layout_marginLeft="20dip" + /> + + </LinearLayout> + </LinearLayout> + +</LinearLayout> diff --git a/OpenKeychain/src/main/res/layout/multi_certify_key_activity.xml b/OpenKeychain/src/main/res/layout/multi_certify_key_activity.xml new file mode 100644 index 000000000..c3eaed9a8 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/multi_certify_key_activity.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <include layout="@layout/notify_area" /> + + <FrameLayout + android:id="@+id/content_frame" + android:layout_marginLeft="@dimen/drawer_content_padding" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <fragment + android:id="@+id/multi_certify_key_fragment" + android:name="org.sufficientlysecure.keychain.ui.MultiCertifyKeyFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + </FrameLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/multi_certify_key_fragment.xml b/OpenKeychain/src/main/res/layout/multi_certify_key_fragment.xml new file mode 100644 index 000000000..d2335cbe9 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/multi_certify_key_fragment.xml @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginBottom="8dp" + android:orientation="horizontal"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/imageView" + android:src="@drawable/ic_action_person" + android:layout_gravity="center_vertical" /> + + <TextView + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/certify_text" + android:id="@+id/textView" + android:layout_weight="1" /> + + </LinearLayout> + + <TextView + style="@style/SectionHeader" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/section_uids_to_certify" /> + + <org.sufficientlysecure.keychain.ui.widget.FixedListView + android:id="@+id/view_key_user_ids" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <TextView + style="@style/SectionHeader" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_marginTop="14dp" + android:text="@string/section_certify" + android:layout_weight="1" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:paddingLeft="8dp" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/add_keys_my_key" + android:paddingRight="8dp" + android:gravity="center_vertical" /> + + <org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner + android:id="@+id/certify_key_spinner" + android:minHeight="56dip" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" /> + + </LinearLayout> + + <CheckBox + android:id="@+id/sign_key_upload_checkbox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:checked="false" + android:text="@string/label_send_key" + android:paddingTop="12dp" + android:paddingBottom="12dp"/> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> + + <LinearLayout + android:id="@+id/certify_key_certify_button" + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:clickable="true" + android:paddingRight="4dp" + style="@style/SelectableItem" + android:orientation="horizontal"> + + <TextView + android:paddingLeft="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="0dip" + android:layout_height="match_parent" + android:text="@string/key_view_action_certify" + android:layout_weight="1" + android:gravity="center_vertical" /> + + <!-- separate ImageView required for recoloring --> + <ImageView + android:id="@+id/certify_key_action_certify_image" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:padding="8dp" + android:src="@drawable/status_signature_verified_cutout" + android:layout_gravity="center_vertical" /> + + </LinearLayout> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginBottom="4dp" + android:background="?android:attr/listDivider" /> + + </LinearLayout> + +</ScrollView>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/recipient_selection_list_entry.xml b/OpenKeychain/src/main/res/layout/recipient_selection_list_entry.xml index 85ed92ef7..57ca9b9d8 100644 --- a/OpenKeychain/src/main/res/layout/recipient_selection_list_entry.xml +++ b/OpenKeychain/src/main/res/layout/recipient_selection_list_entry.xml @@ -43,7 +43,6 @@ android:paddingLeft="16dip" android:singleLine="true" android:ellipsize="end" - android:typeface="monospace" android:layout_marginTop="-4dip" /> </LinearLayout> diff --git a/OpenKeychain/src/main/res/layout/select_key_item.xml b/OpenKeychain/src/main/res/layout/select_key_item.xml index 4fe80c7e1..b6cbfd973 100644 --- a/OpenKeychain/src/main/res/layout/select_key_item.xml +++ b/OpenKeychain/src/main/res/layout/select_key_item.xml @@ -42,22 +42,16 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0xBBBBBBBBBBBBBBB" - android:textAppearance="?android:attr/textAppearanceSmall" - android:typeface="monospace" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </LinearLayout> - - <TextView - android:gravity="right" - android:paddingLeft="4dp" - android:minWidth="90dip" - android:id="@+id/status" - android:paddingTop="4dp" + <ImageView + android:id="@+id/status_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="expired" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textStyle="italic" - android:layout_gravity="right" /> + android:layout_gravity="center" + android:src="@drawable/status_signature_revoked_cutout" + android:paddingLeft="16dp" + android:paddingRight="16dp" /> </LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/select_secret_key_activity.xml b/OpenKeychain/src/main/res/layout/select_secret_key_activity.xml deleted file mode 100644 index c4cdd7576..000000000 --- a/OpenKeychain/src/main/res/layout/select_secret_key_activity.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_centerHorizontal="true" > - - <FrameLayout - android:id="@+id/select_secret_key_fragment_container" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - -</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/select_secret_key_layout_fragment.xml b/OpenKeychain/src/main/res/layout/select_secret_key_layout_fragment.xml deleted file mode 100644 index e5fd3f9f2..000000000 --- a/OpenKeychain/src/main/res/layout/select_secret_key_layout_fragment.xml +++ /dev/null @@ -1,75 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <Button - android:id="@+id/select_secret_key_select_key_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="4dp" - android:layout_marginRight="4dp" - android:layout_marginTop="4dp" - android:text="@string/api_settings_select_key" - android:drawableLeft="@drawable/ic_action_accounts" - android:background="@drawable/button_edgy" - android:textSize="14sp"/> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="4dp" - android:layout_marginLeft="4dp" - android:layout_marginTop="4dp" - android:orientation="vertical" - android:paddingLeft="4dp"> - - <!-- Has been made focusable to display error messages with setError --> - <TextView - android:id="@+id/select_secret_key_user_id" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="left" - android:focusable="true" - android:focusableInTouchMode="true" - android:singleLine="true" - android:ellipsize="end" - android:visibility="gone" - android:layout_marginRight="5dip" - android:text="" - android:textAppearance="?android:attr/textAppearanceSmall" /> - - <TextView - android:id="@+id/select_secret_key_user_id_rest" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="left" - android:ellipsize="end" - android:singleLine="true" - android:layout_marginRight="5dip" - android:text="" - android:visibility="gone" - android:textAppearance="?android:attr/textAppearanceSmall" /> - - <TextView - android:id="@+id/select_secret_key_master_key_hex" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceSmall" - android:visibility="gone" - android:layout_marginRight="15dip" /> - - <TextView - android:id="@+id/no_key_selected" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:textAppearance="?android:attr/textAppearanceSmall" - android:text="@string/api_settings_no_key" - android:layout_marginTop="15dp" /> - - </LinearLayout> - -</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml index 6bcb216d7..691ee357d 100644 --- a/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml @@ -147,7 +147,6 @@ android:id="@+id/view_key_action_update" android:paddingLeft="8dp" android:paddingRight="8dp" - android:layout_marginBottom="8dp" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/OpenKeychain/src/main/res/menu/key_list.xml b/OpenKeychain/src/main/res/menu/key_list.xml index 6e571243d..3e7d6fc9f 100644 --- a/OpenKeychain/src/main/res/menu/key_list.xml +++ b/OpenKeychain/src/main/res/menu/key_list.xml @@ -7,18 +7,23 @@ android:title="@string/menu_search" android:icon="@drawable/ic_action_search" app:actionViewClass="android.support.v7.widget.SearchView" - app:showAsAction="collapseActionView|ifRoom" /> + app:showAsAction="collapseActionView|always" /> + + <item + android:id="@+id/menu_key_list_search_cloud" + app:showAsAction="always|withText" + android:icon="@drawable/ic_action_search_cloud" + android:title="@string/menu_add_keys" /> <item android:id="@+id/menu_key_list_add" - app:showAsAction="ifRoom|withText" - android:icon="@drawable/ic_action_add_person" + app:showAsAction="always|withText" + android:icon="@drawable/ic_action_new_account" android:title="@string/menu_add_keys" /> <item android:id="@+id/menu_key_list_export" - app:showAsAction="ifRoom|withText" - android:icon="@drawable/ic_action_import_export" + app:showAsAction="never" android:title="@string/menu_export_all_keys" /> <item diff --git a/OpenKeychain/src/main/res/values-cs/strings.xml b/OpenKeychain/src/main/res/values-cs/strings.xml index 9f676c6f9..3276eefed 100644 --- a/OpenKeychain/src/main/res/values-cs/strings.xml +++ b/OpenKeychain/src/main/res/values-cs/strings.xml @@ -229,7 +229,7 @@ <string name="error_generic_report_bug">Nastala obecná chyba, prosím vytvořte nový bug report pro OpenKeychain.</string> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">Špatný podpis!</string> - <string name="decrypt_result_signature_unknown_pub_key">Neznámý veřejný klíč</string> + <string name="decrypt_result_signature_missing_key">Neznámý veřejný klíč</string> <string name="decrypt_result_signature_uncertified">Validní podpis (neověřen)</string> <string name="decrypt_result_signature_certified">Validní podpis (ověřen)</string> <string name="decrypt_result_decrypted">Úspěšně rozšifrováno</string> @@ -329,7 +329,7 @@ <string name="view_log">Zobrazit log</string> <string name="import_error_nothing">Nic k importu</string> <string name="import_error_nothing_cancelled">Import zrušen.</string> - <string name="import_with_warnings">, s varováními</string> + <string name="with_warnings">, s varováními</string> <plurals name="import_error"> <item quantity="one">Import selhal!</item> <item quantity="few">Import %d klíčů selhal!</item> @@ -397,10 +397,10 @@ <string name="key_view_tab_certs">Certifikáty</string> <string name="user_id_info_revoked_title">Zneplatněno</string> <string name="user_id_info_revoked_text">Tato identity byla zneplatněna vlastníkem klíče. Klíč již není platný.</string> - <string name="user_id_info_verified_title">Ověřeno</string> - <string name="user_id_info_verified_text">Tato identita byla ověřena.</string> - <string name="user_id_info_not_verified_title">Neověřeno</string> - <string name="user_id_info_not_verified_text">Tato identita nebyla ještě ověřena. Nemůžete si být jisti, jestli identita opravdu odpovídá určité osobě.</string> + <string name="user_id_info_certified_title">Ověřeno</string> + <string name="user_id_info_certified_text">Tato identita byla ověřena.</string> + <string name="user_id_info_uncertified_title">Neověřeno</string> + <string name="user_id_info_uncertified_text">Tato identita nebyla ještě ověřena. Nemůžete si být jisti, jestli identita opravdu odpovídá určité osobě.</string> <string name="user_id_info_invalid_title">Neplatná</string> <string name="user_id_info_invalid_text">S touto identitou je něco v nepořádku!</string> <!--Edit key--> diff --git a/OpenKeychain/src/main/res/values-de/strings.xml b/OpenKeychain/src/main/res/values-de/strings.xml index 1ff3fa024..3753068bc 100644 --- a/OpenKeychain/src/main/res/values-de/strings.xml +++ b/OpenKeychain/src/main/res/values-de/strings.xml @@ -246,7 +246,7 @@ </plurals> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">Ungültige Signatur!</string> - <string name="decrypt_result_signature_unknown_pub_key">Unbekannter öffentlicher Schlüssel</string> + <string name="decrypt_result_signature_missing_key">Unbekannter öffentlicher Schlüssel</string> <string name="decrypt_result_signature_uncertified">Gültige Signatur (nicht beglaubigt)</string> <string name="decrypt_result_signature_certified">Gültige Signatur (beglaubigt)</string> <string name="decrypt_result_decrypted">Erfolgreich entschlüsselt</string> @@ -369,8 +369,8 @@ <string name="view_log">Log ansehen</string> <string name="import_error_nothing">Nichts zu importieren.</string> <string name="import_error_nothing_cancelled">Import abgebrochen.</string> - <string name="import_with_warnings">, mit Warnungen</string> - <string name="import_with_cancelled">. bis abgebrochen wurde</string> + <string name="with_warnings">, mit Warnungen</string> + <string name="with_cancelled">. bis abgebrochen wurde</string> <plurals name="import_error"> <item quantity="one">Importieren fehlgeschlagen!</item> <item quantity="other">Importieren von %d Schlüsseln fehlgeschlagen!</item> @@ -438,10 +438,10 @@ <string name="key_view_tab_certs">Beglaubigungen</string> <string name="user_id_info_revoked_title">Wiederrufen</string> <string name="user_id_info_revoked_text">Diese Identität wurde durch den Schlüsselinhaber wiederrufen. Sie ist nicht mehr gültig.</string> - <string name="user_id_info_verified_title">Überprüft</string> - <string name="user_id_info_verified_text">Diese Identität wurde überprüft.</string> - <string name="user_id_info_not_verified_title">Nicht überprüft</string> - <string name="user_id_info_not_verified_text">Diese Identität wurde noch nicht verifiziert. Du kannst nicht sicher sein, ob diese Identität wirklich zu einer bestimmten Person gehört.</string> + <string name="user_id_info_certified_title">Überprüft</string> + <string name="user_id_info_certified_text">Diese Identität wurde überprüft.</string> + <string name="user_id_info_uncertified_title">Nicht überprüft</string> + <string name="user_id_info_uncertified_text">Diese Identität wurde noch nicht verifiziert. Du kannst nicht sicher sein, ob diese Identität wirklich zu einer bestimmten Person gehört.</string> <string name="user_id_info_invalid_title">Ungültig</string> <string name="user_id_info_invalid_text">Irgend etwas ist mit dieser Identität nicht in Ordnung!</string> <!--Edit key--> diff --git a/OpenKeychain/src/main/res/values-es/strings.xml b/OpenKeychain/src/main/res/values-es/strings.xml index 968ece35a..6bc2abb6a 100644 --- a/OpenKeychain/src/main/res/values-es/strings.xml +++ b/OpenKeychain/src/main/res/values-es/strings.xml @@ -238,7 +238,7 @@ </plurals> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">¡Firma no válida!</string> - <string name="decrypt_result_signature_unknown_pub_key">Clave pública desconocida</string> + <string name="decrypt_result_signature_missing_key">Clave pública desconocida</string> <string name="decrypt_result_signature_uncertified">Firma válida (no certificada)</string> <string name="decrypt_result_signature_certified">Firma válida (certificada)</string> <string name="decrypt_result_decrypted">Descifrado con éxito</string> @@ -357,8 +357,8 @@ <string name="view_log">Ver registro (log)</string> <string name="import_error_nothing">No hay nada que importar.</string> <string name="import_error_nothing_cancelled">Importación cancelada.</string> - <string name="import_with_warnings">, con advertencias</string> - <string name="import_with_cancelled">, hasta que este cancelada</string> + <string name="with_warnings">, con advertencias</string> + <string name="with_cancelled">, hasta que este cancelada</string> <!--Intent labels--> <string name="intent_decrypt_file">Descifrar archivo con OpenKeychain</string> <string name="intent_import_key">Importar clave con OpenKeychain</string> @@ -421,10 +421,10 @@ <string name="key_view_tab_certs">Certificados</string> <string name="user_id_info_revoked_title">Revocada</string> <string name="user_id_info_revoked_text">Esta identidad ha sido revocada por el propietario de la clave. En adelante no es válida.</string> - <string name="user_id_info_verified_title">Verificada</string> - <string name="user_id_info_verified_text">Esta identidad ha sido verificada</string> - <string name="user_id_info_not_verified_title">No verificada</string> - <string name="user_id_info_not_verified_text">Esta identidad no se ha verificado aún. No puede estar seguro de si la identidad realmente corresponde a una persona en concreto.</string> + <string name="user_id_info_certified_title">Verificada</string> + <string name="user_id_info_certified_text">Esta identidad ha sido verificada</string> + <string name="user_id_info_uncertified_title">No verificada</string> + <string name="user_id_info_uncertified_text">Esta identidad no se ha verificado aún. No puede estar seguro de si la identidad realmente corresponde a una persona en concreto.</string> <string name="user_id_info_invalid_title">No válido</string> <string name="user_id_info_invalid_text">¡Algo está mal con esta identidad!</string> <!--Edit key--> diff --git a/OpenKeychain/src/main/res/values-fr/strings.xml b/OpenKeychain/src/main/res/values-fr/strings.xml index 91940c7c3..e1cb9ec24 100644 --- a/OpenKeychain/src/main/res/values-fr/strings.xml +++ b/OpenKeychain/src/main/res/values-fr/strings.xml @@ -238,7 +238,7 @@ </plurals> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">Signature invalide !</string> - <string name="decrypt_result_signature_unknown_pub_key">Clef publique inconnue</string> + <string name="decrypt_result_signature_missing_key">Clef publique inconnue</string> <string name="decrypt_result_signature_uncertified">Signature valide (non certifiée)</string> <string name="decrypt_result_signature_certified">Signature valide (certifiée)</string> <string name="decrypt_result_decrypted">Déchiffré avec succès</string> @@ -357,8 +357,8 @@ <string name="view_log">Consulter le journal</string> <string name="import_error_nothing">Rien à importer.</string> <string name="import_error_nothing_cancelled">Importation annulée.</string> - <string name="import_with_warnings">, avec des avertissements</string> - <string name="import_with_cancelled">, jusqu\'à l\'annulation</string> + <string name="with_warnings">, avec des avertissements</string> + <string name="with_cancelled">, jusqu\'à l\'annulation</string> <!--Intent labels--> <string name="intent_decrypt_file">Déchiffrer le fichier avec OpenKeychain</string> <string name="intent_import_key">Importer la clef avec OpenKeychain</string> @@ -421,10 +421,10 @@ <string name="key_view_tab_certs">Certificats</string> <string name="user_id_info_revoked_title">Révoquée</string> <string name="user_id_info_revoked_text">Cette identité a été révoquée par le propriétaire de la clef. Elle n\'est plus valide.</string> - <string name="user_id_info_verified_title">Vérifiée</string> - <string name="user_id_info_verified_text">Cette identité a été vérifiée.</string> - <string name="user_id_info_not_verified_title">Non vérifiée</string> - <string name="user_id_info_not_verified_text">Cette identité n\'a pas encore été vérifiée. Vous ne pouvez pas être certain si l\'identité corresponds vraiment à une personne spécifique.</string> + <string name="user_id_info_certified_title">Vérifiée</string> + <string name="user_id_info_certified_text">Cette identité a été vérifiée.</string> + <string name="user_id_info_uncertified_title">Non vérifiée</string> + <string name="user_id_info_uncertified_text">Cette identité n\'a pas encore été vérifiée. Vous ne pouvez pas être certain si l\'identité corresponds vraiment à une personne spécifique.</string> <string name="user_id_info_invalid_title">Invalide</string> <string name="user_id_info_invalid_text">Quelque chose ne va pas avec cette identité !</string> <!--Edit key--> diff --git a/OpenKeychain/src/main/res/values-it/strings.xml b/OpenKeychain/src/main/res/values-it/strings.xml index 5c652fd73..5b79461d5 100644 --- a/OpenKeychain/src/main/res/values-it/strings.xml +++ b/OpenKeychain/src/main/res/values-it/strings.xml @@ -245,7 +245,7 @@ Non potrai annullare!</string> </plurals> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">Firma non valida!</string> - <string name="decrypt_result_signature_unknown_pub_key">Chiave pubblica sconosciuta</string> + <string name="decrypt_result_signature_missing_key">Chiave pubblica sconosciuta</string> <string name="decrypt_result_signature_uncertified">Firma valida (non certificata)</string> <string name="decrypt_result_signature_certified">Firma valida (certificata)</string> <string name="decrypt_result_decrypted">Decodificato correttamente</string> @@ -362,7 +362,7 @@ Non potrai annullare!</string> <string name="view_log">Mostra registro</string> <string name="import_error_nothing">Niente da importare</string> <string name="import_error_nothing_cancelled">Importazione cancellata.</string> - <string name="import_with_warnings">, con avvisi</string> + <string name="with_warnings">, con avvisi</string> <!--Intent labels--> <string name="intent_decrypt_file">Decodifica File con OpenKeychain</string> <string name="intent_import_key">Importa Chiave con OpenKeychain</string> @@ -426,10 +426,10 @@ Permetti accesso?\n\nATTENZIONE: Se non sai perche\' questo schermata e\' appars <string name="key_view_tab_certs">Certificati</string> <string name="user_id_info_revoked_title">Revocato</string> <string name="user_id_info_revoked_text">Questa identità è stata revocata dal suo proprietario. Non è più valida.</string> - <string name="user_id_info_verified_title">Verificato</string> - <string name="user_id_info_verified_text">Questa identità è stata verificata.</string> - <string name="user_id_info_not_verified_title">Non verificato</string> - <string name="user_id_info_not_verified_text">Questa identità non è stata ancora verificata. Non puoi esser sicuro che l\'identità corrisponda veramente ad una specifica persona.</string> + <string name="user_id_info_certified_title">Verificato</string> + <string name="user_id_info_certified_text">Questa identità è stata verificata.</string> + <string name="user_id_info_uncertified_title">Non verificato</string> + <string name="user_id_info_uncertified_text">Questa identità non è stata ancora verificata. Non puoi esser sicuro che l\'identità corrisponda veramente ad una specifica persona.</string> <string name="user_id_info_invalid_title">Non valido</string> <string name="user_id_info_invalid_text">C\'è qualcosa che non va con questa identità!</string> <!--Edit key--> diff --git a/OpenKeychain/src/main/res/values-ja/strings.xml b/OpenKeychain/src/main/res/values-ja/strings.xml index d1a90ff8c..86927d621 100644 --- a/OpenKeychain/src/main/res/values-ja/strings.xml +++ b/OpenKeychain/src/main/res/values-ja/strings.xml @@ -254,7 +254,7 @@ </plurals> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">不正な署名です!</string> - <string name="decrypt_result_signature_unknown_pub_key">不明な公開鍵</string> + <string name="decrypt_result_signature_missing_key">不明な公開鍵</string> <string name="decrypt_result_signature_uncertified">正しい署名 (未証明)</string> <string name="decrypt_result_signature_certified">正しい署名 (証明ずみ)</string> <string name="decrypt_result_decrypted">復号化に成功した</string> @@ -371,8 +371,8 @@ <string name="view_log">ログを見る</string> <string name="import_error_nothing">インポートするものがありません。</string> <string name="import_error_nothing_cancelled">インポートをキャンセルしました。</string> - <string name="import_with_warnings">、とワーニング</string> - <string name="import_with_cancelled">、キャンセルされるまで</string> + <string name="with_warnings">、とワーニング</string> + <string name="with_cancelled">、キャンセルされるまで</string> <plurals name="import_error"> <item quantity="other">%d 個の鍵のインポート失敗!</item> </plurals> @@ -443,10 +443,10 @@ <string name="key_view_tab_certs">証明</string> <string name="user_id_info_revoked_title">破棄</string> <string name="user_id_info_revoked_text">このIDは鍵の所有者により破棄されています。もう適正ではありません。</string> - <string name="user_id_info_verified_title">検証</string> - <string name="user_id_info_verified_text">このIDは検証されています。</string> - <string name="user_id_info_not_verified_title">未検証</string> - <string name="user_id_info_not_verified_text">このIDはまだ検証されていません。IDが本当に特定の人に対応している場合を、あなたは確認することができません。</string> + <string name="user_id_info_certified_title">検証</string> + <string name="user_id_info_certified_text">このIDは検証されています。</string> + <string name="user_id_info_uncertified_title">未検証</string> + <string name="user_id_info_uncertified_text">このIDはまだ検証されていません。IDが本当に特定の人に対応している場合を、あなたは確認することができません。</string> <string name="user_id_info_invalid_title">不適正</string> <string name="user_id_info_invalid_text">このIDではなにかしら問題があります!</string> <!--Edit key--> diff --git a/OpenKeychain/src/main/res/values-nl/strings.xml b/OpenKeychain/src/main/res/values-nl/strings.xml index c076bbde4..e45b3b62f 100644 --- a/OpenKeychain/src/main/res/values-nl/strings.xml +++ b/OpenKeychain/src/main/res/values-nl/strings.xml @@ -174,7 +174,7 @@ </plurals> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">Ongeldige handtekening!</string> - <string name="decrypt_result_signature_unknown_pub_key">Onbekende publieke sleutel</string> + <string name="decrypt_result_signature_missing_key">Onbekende publieke sleutel</string> <string name="decrypt_result_signature_uncertified">Geldige handtekening (ongecertificeerd)</string> <string name="decrypt_result_signature_certified">Geldige handtekening (gecertificeerd)</string> <string name="decrypt_result_decrypted">Succesvol gedecodeerd</string> diff --git a/OpenKeychain/src/main/res/values-pl/strings.xml b/OpenKeychain/src/main/res/values-pl/strings.xml index 8effd7f98..e4653d403 100644 --- a/OpenKeychain/src/main/res/values-pl/strings.xml +++ b/OpenKeychain/src/main/res/values-pl/strings.xml @@ -161,7 +161,7 @@ </plurals> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">Nieprawidłowy podpis!</string> - <string name="decrypt_result_signature_unknown_pub_key">Nieznany klucz publiczny</string> + <string name="decrypt_result_signature_missing_key">Nieznany klucz publiczny</string> <string name="decrypt_result_signature_uncertified">Podpis prawidłowy (bez certyfikatu)</string> <string name="decrypt_result_signature_certified">Podpis prawidłowy (z certyfikatem)</string> <string name="decrypt_result_decrypted">Odszyfrowano pomyślnie</string> diff --git a/OpenKeychain/src/main/res/values-ru/strings.xml b/OpenKeychain/src/main/res/values-ru/strings.xml index 5ce53a9f1..303efdf0a 100644 --- a/OpenKeychain/src/main/res/values-ru/strings.xml +++ b/OpenKeychain/src/main/res/values-ru/strings.xml @@ -209,7 +209,7 @@ </plurals> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">Неверная подпись!</string> - <string name="decrypt_result_signature_unknown_pub_key">Неизвестный ключ</string> + <string name="decrypt_result_signature_missing_key">Неизвестный ключ</string> <string name="decrypt_result_signature_uncertified">Верная подпись (не сертифицирована)</string> <string name="decrypt_result_signature_certified">Верная подпись (сертифицирована)</string> <string name="decrypt_result_decrypted">Успешно расшифровано</string> @@ -311,7 +311,7 @@ </plurals> <string name="view_log">Смотреть журнал</string> <string name="import_error_nothing">Нет данных для импорта.</string> - <string name="import_with_warnings">, с предупреждениями</string> + <string name="with_warnings">, с предупреждениями</string> <!--Intent labels--> <string name="intent_decrypt_file">OpenKeychain: Расшифровать файл</string> <string name="intent_import_key">OpenKeychain: Импортировать ключ</string> @@ -361,9 +361,9 @@ <string name="key_view_tab_keys">Доп. ключи</string> <string name="key_view_tab_certs">Сертификация</string> <string name="user_id_info_revoked_title">Аннулировано</string> - <string name="user_id_info_verified_title">Подтверждено</string> - <string name="user_id_info_not_verified_title">Не подтверждено</string> - <string name="user_id_info_not_verified_text">Этот идентификатор не был заверен. Нет гарантии, что он принадлежит этому человеку.</string> + <string name="user_id_info_certified_title">Подтверждено</string> + <string name="user_id_info_uncertified_title">Не подтверждено</string> + <string name="user_id_info_uncertified_text">Этот идентификатор не был заверен. Нет гарантии, что он принадлежит этому человеку.</string> <string name="user_id_info_invalid_title">Недействительно</string> <string name="user_id_info_invalid_text">Что-то не так с идентификатором!</string> <!--Edit key--> diff --git a/OpenKeychain/src/main/res/values-sl/strings.xml b/OpenKeychain/src/main/res/values-sl/strings.xml index 491bf5cf5..9b92ab14f 100644 --- a/OpenKeychain/src/main/res/values-sl/strings.xml +++ b/OpenKeychain/src/main/res/values-sl/strings.xml @@ -226,7 +226,7 @@ </plurals> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">Neveljaven podpis!</string> - <string name="decrypt_result_signature_unknown_pub_key">Neznan javni ključ</string> + <string name="decrypt_result_signature_missing_key">Neznan javni ključ</string> <string name="decrypt_result_signature_uncertified">Veljaven podpis (neoverjen)</string> <string name="decrypt_result_signature_certified">Veljaven podpis (overjen)</string> <string name="decrypt_result_decrypted">Uspešno dešifrirano</string> @@ -319,8 +319,8 @@ <string name="view_log">Poglej dnevnik</string> <string name="import_error_nothing">Nič za uvoziti.</string> <string name="import_error_nothing_cancelled">Uvoz preklican.</string> - <string name="import_with_warnings">, z opozorilom</string> - <string name="import_with_cancelled"> do preklica</string> + <string name="with_warnings">, z opozorilom</string> + <string name="with_cancelled"> do preklica</string> <!--Intent labels--> <string name="intent_decrypt_file">Dešifriraj datoteko z OpenKeychain</string> <string name="intent_import_key">Uvozi ključ z OpenKeychain</string> @@ -380,9 +380,9 @@ <string name="key_view_tab_certs">Certifikati</string> <string name="user_id_info_revoked_title">Preklican</string> <string name="user_id_info_revoked_text">Lastnik ključa je preklical to identiteto. Ta ni več veljavna.</string> - <string name="user_id_info_verified_title">Preverjen</string> - <string name="user_id_info_verified_text">Identiteta je bila preverjena</string> - <string name="user_id_info_not_verified_title">Nepreverjen</string> + <string name="user_id_info_certified_title">Preverjen</string> + <string name="user_id_info_certified_text">Identiteta je bila preverjena</string> + <string name="user_id_info_uncertified_title">Nepreverjen</string> <string name="user_id_info_invalid_title">Neveljaven</string> <!--Edit key--> <string name="edit_key_action_change_passphrase">Zamenjaj geslo</string> diff --git a/OpenKeychain/src/main/res/values-sr/strings.xml b/OpenKeychain/src/main/res/values-sr/strings.xml index c1121ef4d..d77809010 100644 --- a/OpenKeychain/src/main/res/values-sr/strings.xml +++ b/OpenKeychain/src/main/res/values-sr/strings.xml @@ -231,7 +231,7 @@ <string name="error_generic_report_bug">Дошло је до опште грешке, направите нови извештај о грешци за Отворени кључарник.</string> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">Неисправан потпис!</string> - <string name="decrypt_result_signature_unknown_pub_key">Непознат јавни кључ</string> + <string name="decrypt_result_signature_missing_key">Непознат јавни кључ</string> <string name="decrypt_result_signature_uncertified">Исправан потпис (неоверен)</string> <string name="decrypt_result_signature_certified">Исправан потпис (оверен)</string> <string name="decrypt_result_decrypted">Успешно дешифровано</string> @@ -330,8 +330,8 @@ <string name="view_log">Прикажи дневник</string> <string name="import_error_nothing">Нема ништа за увоз.</string> <string name="import_error_nothing_cancelled">Увоз је отказан.</string> - <string name="import_with_warnings">, са упозорењима</string> - <string name="import_with_cancelled">, док није отказано</string> + <string name="with_warnings">, са упозорењима</string> + <string name="with_cancelled">, док није отказано</string> <!--Intent labels--> <string name="intent_decrypt_file">Дешифруј фајл помоћу Отвореног кључарника</string> <string name="intent_import_key">Увези кључ у Отворени кључарник</string> @@ -393,10 +393,10 @@ <string name="key_view_tab_certs">Сертификати</string> <string name="user_id_info_revoked_title">Опозван</string> <string name="user_id_info_revoked_text">Власник кључа је опозвао овај идентитет. Више није исправан.</string> - <string name="user_id_info_verified_title">Оверен</string> - <string name="user_id_info_verified_text">Овај идентитет није оверен.</string> - <string name="user_id_info_not_verified_title">Није оверен</string> - <string name="user_id_info_not_verified_text">Овај идентитет још није оверен. Не можете бити сигурни да идентитет заиста одговара одређеној особи.</string> + <string name="user_id_info_certified_title">Оверен</string> + <string name="user_id_info_certified_text">Овај идентитет није оверен.</string> + <string name="user_id_info_uncertified_title">Није оверен</string> + <string name="user_id_info_uncertified_text">Овај идентитет још није оверен. Не можете бити сигурни да идентитет заиста одговара одређеној особи.</string> <string name="user_id_info_invalid_title">Неисправан</string> <string name="user_id_info_invalid_text">Нешто није у реду са овим идентитетом!</string> <!--Edit key--> diff --git a/OpenKeychain/src/main/res/values-tr/strings.xml b/OpenKeychain/src/main/res/values-tr/strings.xml index 379e2af3c..6a96e4f5e 100644 --- a/OpenKeychain/src/main/res/values-tr/strings.xml +++ b/OpenKeychain/src/main/res/values-tr/strings.xml @@ -214,7 +214,7 @@ <string name="error_generic_report_bug">Genel bir hata oluştu. Lütfen OpenKeychain için bir hata raporu oluşturun.</string> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">Geçersiz imza!</string> - <string name="decrypt_result_signature_unknown_pub_key">Bilinmeyen açık anahtar</string> + <string name="decrypt_result_signature_missing_key">Bilinmeyen açık anahtar</string> <string name="decrypt_result_signature_uncertified">Geçerli imza (sertifikasız)</string> <string name="decrypt_result_signature_certified">Geçerli imza (sertifalı)</string> <string name="decrypt_result_decrypted">Başarıyla çözümlendi</string> @@ -353,10 +353,10 @@ <string name="key_view_tab_share">Paylaş</string> <string name="key_view_tab_keys">Alt anahtarlar</string> <string name="key_view_tab_certs">Sertifikalar</string> - <string name="user_id_info_verified_title">Doğrulanmış</string> - <string name="user_id_info_verified_text">Kimlik doğrulandı.</string> - <string name="user_id_info_not_verified_title">Doğrulanmamış</string> - <string name="user_id_info_not_verified_text">Bu kimlik henüz doğrulanmadı. Bu kimliğin belirli bir kişiye ait olduğundan emin olamazsınız.</string> + <string name="user_id_info_certified_title">Doğrulanmış</string> + <string name="user_id_info_certified_text">Kimlik doğrulandı.</string> + <string name="user_id_info_uncertified_title">Doğrulanmamış</string> + <string name="user_id_info_uncertified_text">Bu kimlik henüz doğrulanmadı. Bu kimliğin belirli bir kişiye ait olduğundan emin olamazsınız.</string> <string name="user_id_info_invalid_title">Geçersiz</string> <string name="user_id_info_invalid_text">Bu kimlikle ilgili yanlış olan bazı şeyler var!</string> <!--Edit key--> diff --git a/OpenKeychain/src/main/res/values-uk/strings.xml b/OpenKeychain/src/main/res/values-uk/strings.xml index f38a542be..46d5f59c8 100644 --- a/OpenKeychain/src/main/res/values-uk/strings.xml +++ b/OpenKeychain/src/main/res/values-uk/strings.xml @@ -208,7 +208,7 @@ </plurals> <!--results shown after decryption/verification--> <string name="decrypt_result_invalid_signature">Невірний підпис!</string> - <string name="decrypt_result_signature_unknown_pub_key">Невідомий відкритий ключ</string> + <string name="decrypt_result_signature_missing_key">Невідомий відкритий ключ</string> <string name="decrypt_result_signature_uncertified">Дійсний підпис (несертифікований)</string> <string name="decrypt_result_signature_certified">Дійсний підпис (сертифікований)</string> <string name="decrypt_result_decrypted">Успішно розшифровано.</string> @@ -327,7 +327,7 @@ </plurals> <string name="view_log">Переглянути журнал</string> <string name="import_error_nothing">Нема що імпортувати.</string> - <string name="import_with_warnings">, із застереженнями</string> + <string name="with_warnings">, із застереженнями</string> <!--Intent labels--> <string name="intent_decrypt_file">Розшифрувати файл з OpenKeychain</string> <string name="intent_import_key">Імпортувати ключ з OpenKeychain</string> @@ -383,10 +383,10 @@ <string name="key_view_tab_certs">Сертифікати</string> <string name="user_id_info_revoked_title">Відхилено</string> <string name="user_id_info_revoked_text">Ця сутність вже відкликана власником ключа. Вона більше не дійсна.</string> - <string name="user_id_info_verified_title">Перевірено</string> - <string name="user_id_info_verified_text">Не перевірено</string> - <string name="user_id_info_not_verified_title">Не перевірено</string> - <string name="user_id_info_not_verified_text">Ця сутність ще не перевірена. Ви не можете переконатися, чи сутність справді відповідає вказаній особі.</string> + <string name="user_id_info_certified_title">Перевірено</string> + <string name="user_id_info_certified_text">Не перевірено</string> + <string name="user_id_info_uncertified_title">Не перевірено</string> + <string name="user_id_info_uncertified_text">Ця сутність ще не перевірена. Ви не можете переконатися, чи сутність справді відповідає вказаній особі.</string> <string name="user_id_info_invalid_title">Недійсна</string> <string name="user_id_info_invalid_text">Щось неправильне у цій сутності!</string> <!--Edit key--> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 9f5d3f535..847dfffb5 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ <string name="title_encrypt_to_file">"Encrypt To File"</string> <string name="title_decrypt_to_file">"Decrypt To File"</string> <string name="title_import_keys">"Import Keys"</string> + <string name="title_add_keys">"Add Keys"</string> <string name="title_export_key">"Export Key"</string> <string name="title_export_keys">"Export Keys"</string> <string name="title_key_not_found">"Key Not Found"</string> @@ -46,10 +47,11 @@ <string name="section_general">"General"</string> <string name="section_defaults">"Defaults"</string> <string name="section_advanced">"Advanced"</string> + <string name="section_certify">"Certify"</string> <string name="section_actions">"Actions"</string> <string name="section_share_key">"Whole key"</string> <string name="section_certification_key">"Your Key used for certification"</string> - <string name="section_upload_key">"Upload Key"</string> + <string name="section_upload_key">"Synchronize Key"</string> <string name="section_key_server">"Keyserver"</string> <string name="section_fingerprint">"Fingerprint"</string> <string name="section_key_to_certify">"Key to be certified"</string> @@ -133,7 +135,7 @@ <string name="label_name">"Name"</string> <string name="label_comment">"Comment"</string> <string name="label_email">"Email"</string> - <string name="label_send_key">"Upload key to selected keyserver after certification"</string> + <string name="label_send_key">"Synchronize with public keyservers"</string> <string name="label_fingerprint">"Fingerprint"</string> <string name="expiry_date_dialog_title">"Set expiry date"</string> <string name="label_first_keyserver_is_used">"(First keyserver listed is preferred)"</string> @@ -234,6 +236,7 @@ <string name="select_key_to_certify">"Please select a key to be used for certification!"</string> <string name="key_too_big_for_sharing">"Key is too big to be shared this way!"</string> <string name="text_copied_to_clipboard">"Text has been copied to the clipboard!"</string> + <string name="select_key_for_exchange">"Please select a key to be used for secure exchange!"</string> <!-- errors @@ -259,28 +262,31 @@ <string name="error_jelly_bean_needed">"You need Android 4.1 to use Android's NFC Beam feature!"</string> <string name="error_nfc_needed">"NFC is not available on your device!"</string> <string name="error_nothing_import">"No keys found!"</string> - <string name="error_query_too_short">"Search query too short. Please refine your query!"</string> - <string name="error_searching_keys">"An error occurred when searching for keys."</string> - <string name="error_too_many_responses">"Key search query returned too many candidates. Please refine your query!"</string> - <string name="error_too_short_or_too_many_responses">"Either no keys or too many have been found. Please improve your query!"</string> <string name="error_contacts_key_id_missing">"Retrieving the key ID from contacts failed!"</string> - - <string name="error_import_no_valid_keys">"No valid keys found in File/Clipboard!"</string> <string name="error_generic_report_bug">"A generic error occurred, please create a new bug report for OpenKeychain."</string> - <plurals name="error_import_non_pgp_part"> - <item quantity="one">"part of the loaded file is a valid OpenPGP object but not a OpenPGP key"</item> - <item quantity="other">"parts of the loaded file are valid OpenPGP objects but not OpenPGP keys"</item> - </plurals> <!-- results shown after decryption/verification --> + <string name="decrypt_result_no_signature">"Not Signed"</string> <string name="decrypt_result_invalid_signature">"Invalid signature!"</string> - <string name="decrypt_result_signature_unknown_pub_key">"Unknown public key"</string> - <string name="decrypt_result_signature_uncertified">"Valid signature (uncertified)"</string> - <string name="decrypt_result_signature_certified">"Valid signature (certified)"</string> - <string name="decrypt_result_decrypted">"Successfully decrypted"</string> - <string name="decrypt_result_decrypted_unknown_pub_key">"Successfully decrypted but unknown public key"</string> - <string name="decrypt_result_decrypted_and_signature_uncertified">"Successfully decrypted and valid signature (uncertified)"</string> - <string name="decrypt_result_decrypted_and_signature_certified">"Successfully decrypted and valid signature (certified)"</string> + <string name="decrypt_result_signature_uncertified">"Signed by (not certified!)"</string> + <string name="decrypt_result_signature_certified">"Signed by"</string> + <string name="decrypt_result_signature_expired_key">"Key is expired!"</string> + <string name="decrypt_result_signature_revoked_key">"Key has been revoked!"</string> + <string name="decrypt_result_signature_missing_key">"Unknown public key"</string> + <string name="decrypt_result_encrypted">"Encrypted"</string> + <string name="decrypt_result_not_encrypted">"Not Encrypted"</string> + <string name="decrypt_result_action_show">"Show"</string> + <string name="decrypt_result_action_Lookup">"Lookup"</string> + <string name="decrypt_invalid_text">"Either the signature is invalid or the key has been revoked/is expired. You can not be sure who wrote the text. Do you still want to display it?"</string> + <string name="decrypt_invalid_button">"I understand the risks, display it!"</string> + + <!-- Add keys --> + <string name="add_keys_section_secure_exchange">"Exchange"</string> + <string name="add_keys_section_secure_add">"Add"</string> + <string name="add_keys_my_key">"My key:"</string> + <string name="add_keys_start_exchange">"Start exchange"</string> + <string name="add_keys_qr_code">"Scan QR Code"</string> + <string name="add_keys_nfc">"Receive via NFC"</string> <!-- progress dialogs, usually ending in '…' --> <string name="progress_done">"Done."</string> @@ -387,6 +393,11 @@ <string name="import_qr_code_too_short_fingerprint">"Fingerprint is too short (< 16 characters)"</string> <string name="import_qr_code_button">"Scan QR Code…"</string> + <!-- Generic result toast --> + <string name="view_log">"View Log"</string> + <string name="with_warnings">", with warnings"</string> + <string name="with_cancelled">", until cancelled"</string> + <!-- Import result toast --> <plurals name="import_keys_added_and_updated_1"> <item quantity="one">"Successfully imported key"</item> @@ -408,15 +419,26 @@ <item quantity="one">"Import failed for one key!"</item> <item quantity="other">"Import failed for %d keys!"</item> </plurals> - <string name="view_log">"View Log"</string> - <string name="import_error_nothing">"Nothing to import."</string> - <string name="import_error_nothing_cancelled">"Import cancelled."</string> - <string name="import_with_warnings">", with warnings"</string> - <string name="import_with_cancelled">", until cancelled"</string> <plurals name="import_error"> <item quantity="one">"Import failed!"</item> <item quantity="other">"Import of %d keys failed!"</item> </plurals> + <string name="import_error_nothing">"Nothing to import."</string> + <string name="import_error_nothing_cancelled">"Import cancelled."</string> + + <!-- Certify result toast --> + <plurals name="certify_keys_ok"> + <item quantity="one">"Successfully certified key%2$s."</item> + <item quantity="other">"Successfully certified %1$d keys%2$s."</item> + </plurals> + <plurals name="certify_keys_with_errors"> + <item quantity="one">"Certification failed!"</item> + <item quantity="other">"Certification failed for %d keys!"</item> + </plurals> + <plurals name="certify_error"> + <item quantity="one">"Certification failed!"</item> + <item quantity="other">"Certification of %d keys failed!"</item> + </plurals> <!-- Intent labels --> <string name="intent_decrypt_file">"Decrypt File with OpenKeychain"</string> @@ -472,6 +494,8 @@ <string name="key_list_empty_text3">"or"</string> <string name="key_list_empty_button_create">"creating your own key"</string> <string name="key_list_empty_button_import">"importing an existing key."</string> + <string name="key_list_filter_show_all">"Show all keys"</string> + <string name="key_list_filter_show_certified">"Show certified keys only"</string> <!-- Key view --> <string name="key_view_action_edit">"Edit key"</string> @@ -488,10 +512,10 @@ <string name="key_view_tab_certs">"Certificates"</string> <string name="user_id_info_revoked_title">"Revoked"</string> <string name="user_id_info_revoked_text">"This identity has been revoked by the key owner. It is no longer valid."</string> - <string name="user_id_info_verified_title">"Verified"</string> - <string name="user_id_info_verified_text">"This identity has been verified."</string> - <string name="user_id_info_not_verified_title">"Not verified"</string> - <string name="user_id_info_not_verified_text">"This identity has not been verified yet. You cannot be sure if the identity really corresponds to a specific person."</string> + <string name="user_id_info_certified_title">"Certified"</string> + <string name="user_id_info_certified_text">"This identity has been certified by you."</string> + <string name="user_id_info_uncertified_title">"Not certified"</string> + <string name="user_id_info_uncertified_text">"This identity has not been certified yet. You cannot be sure if the identity really corresponds to a specific person."</string> <string name="user_id_info_invalid_title">"Invalid"</string> <string name="user_id_info_invalid_text">"Something is wrong with this identity!"</string> @@ -769,7 +793,7 @@ <string name="msg_mf_subkey_strip">"Stripping subkey %s"</string> <string name="msg_mf_success">"Keyring successfully modified"</string> <string name="msg_mf_uid_add">"Adding user id %s"</string> - <string name="msg_mf_uid_primary">"Changing primary uid to %s"</string> + <string name="msg_mf_uid_primary">"Changing primary user id to %s"</string> <string name="msg_mf_uid_revoke">"Revoking user id %s"</string> <string name="msg_mf_uid_error_empty">"User ID must not be empty!"</string> <string name="msg_mf_unlock_error">"Error unlocking keyring!"</string> @@ -875,11 +899,42 @@ <string name="msg_se">"Starting sign and/or encrypt operation"</string> <string name="msg_se_symmetric">"Preparing symmetric encryption"</string> - <string name="msg_crt_upload_success">"Successfully uploaded key to server"</string> + <string name="msg_crt_certifying">"Generating certifications"</string> + <string name="msg_crt_certify_all">"Certifying all user ids for key %s"</string> + <plurals name="msg_crt_certify_some"> + <item quantity="one">"Certifying one user id for key %2$s"</item> + <item quantity="other">"Certifying %1$d user ids for key %2$s"</item> + </plurals> + <string name="msg_crt_error_master_not_found">"Master key not found!"</string> + <string name="msg_crt_error_nothing">"No keys certified!"</string> + <string name="msg_crt_error_unlock">"Error unlocking master key!"</string> + <string name="msg_crt">"Certifying keyrings"</string> + <string name="msg_crt_master_fetch">"Fetching certifying master key"</string> + <string name="msg_crt_save">"Saving certified key %s"</string> + <string name="msg_crt_saving">"Saving keyrings"</string> + <string name="msg_crt_unlock">"Unlocking master key"</string> <string name="msg_crt_success">"Successfully certified identities"</string> + <string name="msg_crt_warn_not_found">"Key not found!"</string> + <string name="msg_crt_warn_cert_failed">"Certificate generation failed!"</string> + <string name="msg_crt_warn_save_failed">"Save operation failed!"</string> + + <string name="msg_crt_upload_success">"Successfully uploaded key to server"</string> <string name="msg_acc_saved">"Account saved"</string> + <string name="msg_download_success">"Downloaded successfully!"</string> + <string name="msg_download_no_valid_keys">"No valid keys found in File/Clipboard!"</string> + <string name="msg_download_no_pgp_parts">"TODO: plurals!"</string> + <plurals name="error_import_non_pgp_part"> + <item quantity="one">"part of the loaded file is a valid OpenPGP object but not a OpenPGP key"</item> + <item quantity="other">"parts of the loaded file are valid OpenPGP objects but not OpenPGP keys"</item> + </plurals> + <string name="msg_download_query_too_short">"Search query too short. Please refine your query!"</string> + <string name="msg_download_too_many_responses">"Key search query returned too many candidates. Please refine your query!"</string> + <string name="msg_download_query_too_short_or_too_many_responses">"Either no keys or too many have been found. Please improve your query!"</string> + + <string name="msg_download_query_failed">"An error occurred when searching for keys."</string> + <!-- PassphraseCache --> <string name="passp_cache_notif_click_to_clear">"Click to clear cached passphrases"</string> <string name="passp_cache_notif_n_keys">"OpenKeychain has cached %d passphrases"</string> @@ -894,7 +949,8 @@ <string name="unknown_uid">"<unknown>"</string> <string name="empty_certs">"No certificates for this key"</string> <string name="certs_text">"Only validated self-certificates and validated certificates created with your keys are displayed here."</string> - <string name="section_uids_to_certify">"Identities to certify"</string> + <string name="section_uids_to_certify">"Identities"</string> + <string name="certify_text">"Do the selected identities match the persons you are exchanging keys with? Deselect all unknown ones."</string> <string name="label_revocation">"Revocation Reason"</string> <string name="label_verify_status">"Verification Status"</string> <string name="label_cert_type">"Type"</string> @@ -914,6 +970,7 @@ <string name="swipe_to_update">"Swipe down to update from keyserver"</string> <string name="error_no_file_selected">"Select at least one file to encrypt!"</string> <string name="error_multi_not_supported">"Saving of multiple files not supported. This is a limitation on current Android."</string> + <string name="key_colon">"Key:"</string> <!-- First Time --> <string name="first_time_text1">"Take back your privacy with OpenKeychain!"</string> |