diff options
85 files changed, 3452 insertions, 1036 deletions
diff --git a/.gitmodules b/.gitmodules index 20a0f60e0..a549f6cec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,6 +25,9 @@ [submodule "extern/openkeychain-api-lib"] path = extern/openkeychain-api-lib url = https://github.com/open-keychain/openkeychain-api-lib.git +[submodule "extern/SuperToasts"] + path = extern/SuperToasts + url = https://github.com/open-keychain/SuperToasts.git [submodule "extern/dnsjava"] path = extern/dnsjava url = https://github.com/open-keychain/dnsjava.git diff --git a/.travis.yml b/.travis.yml index df700368c..36e8f8fcf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,13 +4,13 @@ before_install: # Install base Android SDK - sudo apt-get update -qq - if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm lib32z1 lib32stdc++6; fi - - wget http://dl.google.com/android/android-sdk_r22.3-linux.tgz - - tar xzf android-sdk_r22.3-linux.tgz + - wget http://dl.google.com/android/android-sdk_r22.6.2-linux.tgz + - tar xzf android-sdk_r22.6.2-linux.tgz - export ANDROID_HOME=$PWD/android-sdk-linux - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools # Install required Android components. - - echo "y" | android update sdk -a --filter build-tools-19.0.3,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force + - echo "y" | android update sdk -a --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force install: echo "Installation done" script: gradle assemble -S -q diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 20e5ca594..ba527948a 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'android' -apply plugin: 'android-test' +//apply plugin: 'android-test' sourceSets { - androidTest { - java.srcDir file('src/test/java') + //androidTest { + //java.srcDir file('src/test/java') // configure the set of classes for JUnit tests // include '**/*Test.class' - } + //} } dependencies { @@ -26,10 +26,11 @@ dependencies { compile project(':extern:spongycastle:pkix') compile project(':extern:spongycastle:prov') compile project(':extern:AppMsg:library') + compile project(':extern:SuperToasts:supertoasts') compile project(':extern:dnsjava') // Dependencies for the `instrumentTest` task, make sure to list all your global dependencies here as well - androidTestCompile 'junit:junit:4.10' + androidTestCompile 'junit:junit:4.11' androidTestCompile 'org.robolectric:robolectric:2.3' androidTestCompile 'com.squareup:fest-android:1.0.8' androidTestCompile 'com.google.android:android:4.1.1.4' @@ -47,11 +48,13 @@ dependencies { androidTestCompile project(':extern:spongycastle:pkix') androidTestCompile project(':extern:spongycastle:prov') androidTestCompile project(':extern:AppMsg:library') + androidTestCompile project(':extern:SuperToasts:supertoasts') + } android { compileSdkVersion 19 - buildToolsVersion "19.0.3" + buildToolsVersion "19.1" defaultConfig { minSdkVersion 9 diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 31c809334..b3a4d5960 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -445,6 +445,12 @@ </intent-filter> </service> + <activity + android:name=".ui.LogDisplayActivity" + android:configChanges="orientation|screenSize|keyboardHidden|keyboard" + android:label="@string/title_log_display" + android:exported="false" /> + <service android:name=".service.DummyAccountService"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator"/> diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java index d64587578..f04d84315 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.helper; +import android.content.Context; import android.os.Bundle; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -67,4 +68,12 @@ public class OtherHelper { return sb; } + public static int dpToPx(Context context, int dp) { + return (int) ((dp * context.getResources().getDisplayMetrics().density) + 0.5); + } + + public static int pxToDp(Context context, int px) { + return (int) ((px / context.getResources().getDisplayMetrics().density) + 0.5); + } + } 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 7a7c89301..47265c3aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -251,7 +251,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { this.mKeyId = key.getKeyId(); this.mKeyIdHex = PgpKeyHelper.convertKeyIdToHex(mKeyId); - this.mRevoked = key.maybeRevoked(); + this.mRevoked = key.isRevoked(); this.mFingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()); this.mBitStrength = key.getBitStrength(); final int algorithm = key.getAlgorithm(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java index 5da6c4cd3..fdf561aaf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java @@ -3,7 +3,7 @@ package org.sufficientlysecure.keychain.keyimport; import android.os.Parcel; import android.os.Parcelable; -/** This is a trivial wrapper around UncachedKeyRing which implements Parcelable. It exists +/** This is a trivial wrapper around keyring bytes which implements Parcelable. It exists * for the sole purpose of keeping spongycastle and android imports in separate packages. */ public class ParcelableKeyRing implements Parcelable { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OperationResultParcel.java deleted file mode 100644 index 216e4b497..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OperationResultParcel.java +++ /dev/null @@ -1,169 +0,0 @@ -package org.sufficientlysecure.keychain.pgp; - -import android.os.Parcel; -import android.os.Parcelable; - -import org.sufficientlysecure.keychain.R; - -import java.util.ArrayList; - -/** Represent the result of an operation. - * - * This class holds a result and the log of an operation. It can be subclassed - * to include typed additional information specific to the operation. To keep - * the class structure (somewhat) simple, this class contains an exhaustive - * list (ie, enum) of all possible log types, which should in all cases be tied - * to string resource ids. - * - */ -public class OperationResultParcel implements Parcelable { - /** Holds the overall result. A value of 0 is considered a success, all - * other values may represent failure or varying degrees of success. */ - final int mResult; - - /// A list of log entries tied to the operation result. - final ArrayList<LogEntryParcel> mLog; - - public OperationResultParcel(int result, ArrayList<LogEntryParcel> log) { - mResult = result; - mLog = log; - } - - public OperationResultParcel(Parcel source) { - mResult = source.readInt(); - mLog = source.createTypedArrayList(LogEntryParcel.CREATOR); - } - - public boolean isSuccessful() { - return mResult == 0; - } - - /** One entry in the log. */ - public static class LogEntryParcel implements Parcelable { - final LogLevel mLevel; - final LogType mType; - final String[] mParameters; - final int mIndent; - - public LogEntryParcel(LogLevel level, LogType type, String[] parameters, int indent) { - mLevel = level; - mType = type; - mParameters = parameters; - mIndent = indent; - } - public LogEntryParcel(LogLevel level, LogType type, String[] parameters) { - this(level, type, parameters, 0); - } - - public LogEntryParcel(Parcel source) { - mLevel = LogLevel.values()[source.readInt()]; - mType = LogType.values()[source.readInt()]; - mParameters = source.createStringArray(); - mIndent = source.readInt(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mLevel.ordinal()); - dest.writeInt(mType.ordinal()); - dest.writeStringArray(mParameters); - dest.writeInt(mIndent); - } - - public static final Creator<LogEntryParcel> CREATOR = new Creator<LogEntryParcel>() { - public LogEntryParcel createFromParcel(final Parcel source) { - return new LogEntryParcel(source); - } - - public LogEntryParcel[] newArray(final int size) { - return new LogEntryParcel[size]; - } - }; - - } - - public static enum LogType { - MSG_IP_APPLY_BATCH (R.string.msg_ip_apply_batch), - MSG_IP_BAD_TYPE_SECRET (R.string.msg_ip_bad_type_secret), - MSG_IP_DELETE_OLD_FAIL (R.string.msg_ip_delete_old_fail), - MSG_IP_DELETE_OLD_OK (R.string.msg_ip_delete_old_ok), - MSG_IP_ENCODE_FAIL (R.string.msg_ip_encode_fail), - MSG_IP_FAIL_IO_EXC (R.string.msg_ip_fail_io_exc), - MSG_IP_FAIL_OP_EX (R.string.msg_ip_fail_op_ex), - MSG_IP_FAIL_REMOTE_EX (R.string.msg_ip_fail_remote_ex), - MSG_IP_IMPORTING (R.string.msg_ip_importing), - MSG_IP_INSERT_KEYRING (R.string.msg_ip_insert_keyring), - MSG_IP_INSERT_SUBKEY (R.string.msg_ip_insert_subkey), - MSG_IP_INSERT_SUBKEYS (R.string.msg_ip_insert_subkeys), - MSG_IP_PRESERVING_SECRET (R.string.msg_ip_preserving_secret), - MSG_IP_REINSERT_SECRET (R.string.msg_ip_reinsert_secret), - MSG_IP_SUCCESS (R.string.msg_ip_success), - MSG_IP_TRUST_RETRIEVE (R.string.msg_ip_trust_retrieve), - MSG_IP_TRUST_USING (R.string.msg_ip_trust_using), - MSG_IP_TRUST_USING_SEC (R.string.msg_ip_trust_using_sec), - MSG_IP_UID_CERT_BAD (R.string.msg_ip_uid_cert_bad), - MSG_IP_UID_CERT_ERROR (R.string.msg_ip_uid_cert_error), - MSG_IP_UID_CERT_GOOD (R.string.msg_ip_uid_cert_good), - MSG_IP_UID_CERTS_UNKNOWN (R.string.msg_ip_uid_certs_unknown), - MSG_IP_UID_CLASSIFYING (R.string.msg_ip_uid_classifying), - MSG_IP_UID_INSERT (R.string.msg_ip_uid_insert), - MSG_IP_UID_PROCESSING (R.string.msg_ip_uid_processing), - MSG_IP_UID_SELF_BAD (R.string.msg_ip_uid_self_bad), - MSG_IP_UID_SELF_GOOD (R.string.msg_ip_uid_self_good), - MSG_IP_UID_SELF_IGNORING_OLD (R.string.msg_ip_uid_self_ignoring_old), - MSG_IP_UID_SELF_NEWER (R.string.msg_ip_uid_self_newer), - MSG_IS_BAD_TYPE_PUBLIC (R.string.msg_is_bad_type_public), - MSG_IS_IMPORTING (R.string.msg_is_importing), - MSG_IS_IMPORTING_SUBKEYS (R.string.msg_is_importing_subkeys), - MSG_IS_IO_EXCPTION (R.string.msg_is_io_excption), - MSG_IS_SUBKEY_NONEXISTENT (R.string.msg_is_subkey_nonexistent), - MSG_IS_SUBKEY_OK (R.string.msg_is_subkey_ok), - MSG_IS_SUBKEY_STRIPPED (R.string.msg_is_subkey_stripped), - MSG_IS_SUCCESS (R.string.msg_is_success), - ; - - private final int mMsgId; - LogType(int msgId) { - mMsgId = msgId; - } - public int getMsgId() { - return mMsgId; - } - } - - /** Enumeration of possible log levels. */ - public static enum LogLevel { - DEBUG, - INFO, - WARN, - /** If any ERROR log entry is included in the result, the overall operation should have failed. */ - ERROR, - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mResult); - dest.writeTypedList(mLog); - } - - public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() { - public OperationResultParcel createFromParcel(final Parcel source) { - return new OperationResultParcel(source); - } - - public OperationResultParcel[] newArray(final int size) { - return new OperationResultParcel[size]; - } - }; - -} 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 5ce0b11dd..9b070175c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -33,7 +33,11 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; +import org.sufficientlysecure.keychain.service.OperationResults.ImportResult; +import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -55,10 +59,6 @@ public class PgpImportExport { private ProviderHelper mProviderHelper; - public static final int RETURN_OK = 0; - public static final int RETURN_BAD = -2; - public static final int RETURN_UPDATED = 1; - public PgpImportExport(Context context, Progressable progressable) { super(); this.mContext = context; @@ -115,28 +115,23 @@ public class PgpImportExport { if (aos != null) { aos.close(); } - if (bos != null) { - bos.close(); - } + bos.close(); } catch (IOException e) { + // this is just a finally thing, no matter if it doesn't work out. } } } - /** - * Imports keys from given data. If keyIds is given only those are imported - */ - public Bundle importKeyRings(List<ParcelableKeyRing> entries) + /** Imports keys from given data. If keyIds is given only those are imported */ + public ImportResult importKeyRings(List<ParcelableKeyRing> entries) throws PgpGeneralException, PGPException, IOException { - Bundle returnData = new Bundle(); updateProgress(R.string.progress_importing, 0, 100); - int newKeys = 0; - int oldKeys = 0; - int badKeys = 0; + int newKeys = 0, oldKeys = 0, badKeys = 0; int position = 0; + int progSteps = 100 / entries.size(); for (ParcelableKeyRing entry : entries) { try { UncachedKeyRing key = UncachedKeyRing.decodeFromData(entry.getBytes()); @@ -152,15 +147,21 @@ public class PgpImportExport { } } - mProviderHelper.resetLog(); - OperationResultParcel result = mProviderHelper.savePublicKeyRing(key); - for(OperationResultParcel.LogEntryParcel loge : result.mLog) { - Log.d(Constants.TAG, - loge.mIndent - + new String(new char[loge.mIndent]).replace("\0", " ") - + mContext.getString(loge.mType.getMsgId(), (Object[]) loge.mParameters)); + SaveKeyringResult result; + if (key.isSecret()) { + result = mProviderHelper.saveSecretKeyRing(key, + new ProgressScaler(mProgressable, position, (position+1)*progSteps, 100)); + } else { + result = mProviderHelper.savePublicKeyRing(key, + new ProgressScaler(mProgressable, position, (position+1)*progSteps, 100)); + } + if (!result.success()) { + badKeys += 1; + } else if (result.updated()) { + oldKeys += 1; + } else { + newKeys += 1; } - newKeys += 1; } catch (PgpGeneralException e) { Log.e(Constants.TAG, "Encountered bad key on import!", e); @@ -168,14 +169,33 @@ public class PgpImportExport { } // update progress position++; - updateProgress(position / entries.size() * 100, 100); } - returnData.putInt(KeychainIntentService.RESULT_IMPORT_ADDED, newKeys); - returnData.putInt(KeychainIntentService.RESULT_IMPORT_UPDATED, oldKeys); - returnData.putInt(KeychainIntentService.RESULT_IMPORT_BAD, badKeys); + OperationLog log = mProviderHelper.getLog(); + int resultType = 0; + // special return case: no new keys at all + if (badKeys == 0 && newKeys == 0 && oldKeys == 0) { + resultType = ImportResult.RESULT_FAIL_NOTHING; + } else { + if (newKeys > 0) { + resultType |= ImportResult.RESULT_OK_NEWKEYS; + } + if (oldKeys > 0) { + resultType |= ImportResult.RESULT_OK_UPDATED; + } + if (badKeys > 0) { + resultType |= ImportResult.RESULT_WITH_ERRORS; + if (newKeys == 0 && oldKeys == 0) { + resultType |= ImportResult.RESULT_ERROR; + } + } + if (log.containsWarnings()) { + resultType |= ImportResult.RESULT_WITH_WARNINGS; + } + } + + return new ImportResult(resultType, log, newKeys, oldKeys, badKeys); - return returnData; } public Bundle exportKeyRings(ArrayList<Long> publicKeyRingMasterIds, 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 1264c8c36..a1c6b158b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -2,14 +2,23 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.S2K; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.openpgp.PGPKeyFlags; import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureList; import org.spongycastle.openpgp.PGPUtil; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -18,10 +27,13 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; +import java.util.TreeSet; import java.util.Vector; /** Wrapper around PGPKeyRing class, to be constructed from bytes. @@ -41,24 +53,27 @@ import java.util.Vector; * @see org.sufficientlysecure.keychain.pgp.UncachedSecretKey * */ +@SuppressWarnings("unchecked") public class UncachedKeyRing { final PGPKeyRing mRing; final boolean mIsSecret; + final boolean mIsCanonicalized; UncachedKeyRing(PGPKeyRing ring) { mRing = ring; mIsSecret = ring instanceof PGPSecretKeyRing; + mIsCanonicalized = false; } - public long getMasterKeyId() { - return mRing.getPublicKey().getKeyID(); + private UncachedKeyRing(PGPKeyRing ring, boolean canonicalized) { + mRing = ring; + mIsSecret = ring instanceof PGPSecretKeyRing; + mIsCanonicalized = canonicalized; } - /* TODO don't use this */ - @Deprecated - public PGPKeyRing getRing() { - return mRing; + public long getMasterKeyId() { + return mRing.getPublicKey().getKeyID(); } public UncachedPublicKey getPublicKey() { @@ -85,6 +100,10 @@ public class UncachedKeyRing { return mIsSecret; } + public boolean isCanonicalized() { + return mIsCanonicalized; + } + public byte[] getEncoded() throws IOException { return mRing.getEncoded(); } @@ -93,15 +112,6 @@ public class UncachedKeyRing { return mRing.getPublicKey().getFingerprint(); } - public static UncachedKeyRing decodePublicFromData(byte[] data) - throws PgpGeneralException, IOException { - UncachedKeyRing ring = decodeFromData(data); - if(ring.isSecret()) { - throw new PgpGeneralException("Object not recognized as PGPPublicKeyRing!"); - } - return ring; - } - public static UncachedKeyRing decodeFromData(byte[] data) throws PgpGeneralException, IOException { BufferedInputStream bufferedInput = @@ -169,4 +179,605 @@ public class UncachedKeyRing { return result; } + /** "Canonicalizes" a public key, removing inconsistencies in the process. This variant can be + * applied to public keyrings only. + * + * More specifically: + * - Remove all non-verifying self-certificates + * - Remove all "future" self-certificates + * - Remove all certificates flagged as "local" + * - Remove all certificates which are superseded by a newer one on the same target, + * including revocations with later re-certifications. + * - Remove all certificates of unknown type: + * - key revocation signatures on the master key + * - subkey binding signatures for subkeys + * - certifications and certification revocations for user ids + * - If a subkey retains no valid subkey binding certificate, remove it + * - If a user id retains no valid self certificate, remove it + * - If the key is a secret key, remove all certificates by foreign keys + * - If no valid user id remains, log an error and return null + * + * This operation writes an OperationLog which can be used as part of a OperationResultParcel. + * + * @return A canonicalized key, or null on fatal error + * + */ + @SuppressWarnings("ConstantConditions") + public UncachedKeyRing canonicalize(OperationLog log, int indent) { + + log.add(LogLevel.START, isSecret() ? LogType.MSG_KC_SECRET : LogType.MSG_KC_PUBLIC, + new String[]{PgpKeyHelper.convertKeyIdToHex(getMasterKeyId())}, indent); + indent += 1; + + final Date now = new Date(); + + int redundantCerts = 0, badCerts = 0; + + PGPKeyRing ring = mRing; + PGPPublicKey masterKey = mRing.getPublicKey(); + final long masterKeyId = masterKey.getKeyID(); + + { + log.add(LogLevel.DEBUG, LogType.MSG_KC_MASTER, + new String[]{PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())}, indent); + indent += 1; + + PGPPublicKey modified = masterKey; + PGPSignature revocation = null; + for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getSignatures())) { + int type = zert.getSignatureType(); + + // Disregard certifications on user ids, we will deal with those later + if (type == PGPSignature.NO_CERTIFICATION + || type == PGPSignature.DEFAULT_CERTIFICATION + || type == PGPSignature.CASUAL_CERTIFICATION + || type == PGPSignature.POSITIVE_CERTIFICATION + || type == PGPSignature.CERTIFICATION_REVOCATION) { + continue; + } + WrappedSignature cert = new WrappedSignature(zert); + + if (type != PGPSignature.KEY_REVOCATION) { + // Unknown type, just remove + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TYPE, new String[]{ + "0x" + Integer.toString(type, 16) + }, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; + } + + if (cert.getCreationTime().after(now)) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; + } + + if (cert.isLocal()) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; + } + + try { + cert.init(masterKey); + if (!cert.verifySignature(masterKey)) { + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; + } + } catch (PgpGeneralException e) { + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_ERR, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; + } + + // first revocation? fine then. + if (revocation == null) { + revocation = zert; + // more revocations? at least one is superfluous, then. + } else if (revocation.getCreationTime().before(zert.getCreationTime())) { + modified = PGPPublicKey.removeCertification(modified, revocation); + redundantCerts += 1; + log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, null, indent); + revocation = zert; + } else { + modified = PGPPublicKey.removeCertification(modified, zert); + redundantCerts += 1; + log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, null, indent); + } + } + + for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) { + PGPSignature selfCert = null; + revocation = null; + + // look through signatures for this specific key + for (PGPSignature zert : new IterableIterator<PGPSignature>( + masterKey.getSignaturesForID(userId))) { + WrappedSignature cert = new WrappedSignature(zert); + long certId = cert.getKeyId(); + + int type = zert.getSignatureType(); + if (type != PGPSignature.DEFAULT_CERTIFICATION + && type != PGPSignature.NO_CERTIFICATION + && type != PGPSignature.CASUAL_CERTIFICATION + && type != PGPSignature.POSITIVE_CERTIFICATION + && type != PGPSignature.CERTIFICATION_REVOCATION) { + log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_TYPE, + new String[] { + "0x" + Integer.toString(zert.getSignatureType(), 16) + }, indent); + modified = PGPPublicKey.removeCertification(modified, userId, zert); + badCerts += 1; + } + + if (cert.getCreationTime().after(now)) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; + } + + if (cert.isLocal()) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; + } + + // If this is a foreign signature, ... + if (certId != masterKeyId) { + // never mind any further for public keys, but remove them from secret ones + if (isSecret()) { + log.add(LogLevel.WARN, LogType.MSG_KC_UID_FOREIGN, + new String[] { PgpKeyHelper.convertKeyIdToHex(certId) }, indent); + modified = PGPPublicKey.removeCertification(modified, userId, zert); + badCerts += 1; + } + continue; + } + + // Otherwise, first make sure it checks out + try { + cert.init(masterKey); + if (!cert.verifySignature(masterKey, userId)) { + log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD, + new String[] { userId }, indent); + modified = PGPPublicKey.removeCertification(modified, userId, zert); + badCerts += 1; + continue; + } + } catch (PgpGeneralException e) { + log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_ERR, + new String[] { userId }, indent); + modified = PGPPublicKey.removeCertification(modified, userId, zert); + badCerts += 1; + continue; + } + + switch (type) { + case PGPSignature.DEFAULT_CERTIFICATION: + case PGPSignature.NO_CERTIFICATION: + case PGPSignature.CASUAL_CERTIFICATION: + case PGPSignature.POSITIVE_CERTIFICATION: + if (selfCert == null) { + selfCert = zert; + } else if (selfCert.getCreationTime().before(cert.getCreationTime())) { + modified = PGPPublicKey.removeCertification(modified, userId, selfCert); + redundantCerts += 1; + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP, + new String[] { userId }, indent); + selfCert = zert; + } else { + modified = PGPPublicKey.removeCertification(modified, userId, zert); + redundantCerts += 1; + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP, + new String[] { userId }, indent); + } + // If there is a revocation certificate, and it's older than this, drop it + if (revocation != null + && revocation.getCreationTime().before(selfCert.getCreationTime())) { + modified = PGPPublicKey.removeCertification(modified, userId, revocation); + revocation = null; + redundantCerts += 1; + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD, + new String[] { userId }, indent); + } + break; + + case PGPSignature.CERTIFICATION_REVOCATION: + // If this is older than the (latest) self cert, drop it + if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) { + modified = PGPPublicKey.removeCertification(modified, userId, zert); + redundantCerts += 1; + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD, + new String[] { userId }, indent); + continue; + } + // first revocation? remember it. + if (revocation == null) { + revocation = zert; + // more revocations? at least one is superfluous, then. + } else if (revocation.getCreationTime().before(cert.getCreationTime())) { + modified = PGPPublicKey.removeCertification(modified, userId, revocation); + redundantCerts += 1; + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP, + new String[] { userId }, indent); + revocation = zert; + } else { + modified = PGPPublicKey.removeCertification(modified, userId, zert); + redundantCerts += 1; + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP, + new String[] { userId }, indent); + } + break; + + } + + } + + // If no valid certificate (if only a revocation) remains, drop it + if (selfCert == null && revocation == null) { + modified = PGPPublicKey.removeCertification(modified, userId); + log.add(LogLevel.ERROR, LogType.MSG_KC_UID_REVOKE_DUP, + new String[] { userId }, indent); + } + } + + // If NO user ids remain, error out! + if (!modified.getUserIDs().hasNext()) { + log.add(LogLevel.ERROR, LogType.MSG_KC_FATAL_NO_UID, null, indent); + return null; + } + + // Replace modified key in the keyring + ring = replacePublicKey(ring, modified); + indent -= 1; + + } + + // Process all keys + for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(ring.getPublicKeys())) { + // Don't care about the master key here, that one gets special treatment above + if (key.isMasterKey()) { + continue; + } + log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB, + new String[]{PgpKeyHelper.convertKeyIdToHex(key.getKeyID())}, indent); + indent += 1; + // A subkey needs exactly one subkey binding certificate, and optionally one revocation + // certificate. + PGPPublicKey modified = key; + PGPSignature selfCert = null, revocation = null; + uids: for (PGPSignature zert : new IterableIterator<PGPSignature>(key.getSignatures())) { + // remove from keyring (for now) + modified = PGPPublicKey.removeCertification(modified, zert); + + WrappedSignature cert = new WrappedSignature(zert); + int type = cert.getSignatureType(); + + // filter out bad key types... + if (cert.getKeyId() != masterKey.getKeyID()) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_KEYID, null, indent); + badCerts += 1; + continue; + } + + if (type != PGPSignature.SUBKEY_BINDING && type != PGPSignature.SUBKEY_REVOCATION) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_TYPE, new String[]{ + "0x" + Integer.toString(type, 16) + }, indent); + badCerts += 1; + continue; + } + + if (cert.getCreationTime().after(now)) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_TIME, null, indent); + badCerts += 1; + continue; + } + + if (cert.isLocal()) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_LOCAL, null, indent); + badCerts += 1; + continue; + } + + if (type == PGPSignature.SUBKEY_BINDING) { + + // make sure the certificate checks out + try { + cert.init(masterKey); + if (!cert.verifySignature(masterKey, key)) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD, null, indent); + badCerts += 1; + continue; + } + } catch (PgpGeneralException e) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_ERR, null, indent); + badCerts += 1; + continue; + } + + if (zert.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) { + int flags = ((KeyFlags) zert.getHashedSubPackets() + .getSubpacket(SignatureSubpacketTags.KEY_FLAGS)).getFlags(); + // If this subkey is allowed to sign data, + if ((flags & PGPKeyFlags.CAN_SIGN) == PGPKeyFlags.CAN_SIGN) { + try { + PGPSignatureList list = zert.getUnhashedSubPackets().getEmbeddedSignatures(); + boolean ok = false; + for (int i = 0; i < list.size(); i++) { + WrappedSignature subsig = new WrappedSignature(list.get(i)); + if (subsig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) { + subsig.init(key); + if (subsig.verifySignature(masterKey, key)) { + ok = true; + } else { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD, null, indent); + badCerts += 1; + continue uids; + } + } + } + if (!ok) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, null, indent); + badCerts += 1; + continue; + } + } catch (Exception e) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD_ERR, null, indent); + badCerts += 1; + continue; + } + } + } + + // if we already have a cert, and this one is not newer: skip it + if (selfCert != null && selfCert.getCreationTime().before(cert.getCreationTime())) { + redundantCerts += 1; + continue; + } + + selfCert = zert; + // if this is newer than a possibly existing revocation, drop that one + if (revocation != null && selfCert.getCreationTime().after(revocation.getCreationTime())) { + revocation = null; + } + + // it must be a revocation, then (we made sure above) + } else { + + // make sure the certificate checks out + try { + cert.init(masterKey); + if (!cert.verifySignature(key)) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD, null, indent); + badCerts += 1; + continue; + } + } catch (PgpGeneralException e) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD_ERR, null, indent); + badCerts += 1; + continue; + } + + // if there is no binding (yet), or the revocation is newer than the binding: keep it + if (selfCert != null && selfCert.getCreationTime().after(cert.getCreationTime())) { + redundantCerts += 1; + continue; + } + + revocation = zert; + } + } + + // it is not properly bound? error! + if (selfCert == null) { + ring = replacePublicKey(ring, modified); + + log.add(LogLevel.ERROR, LogType.MSG_KC_SUB_NO_CERT, + new String[]{ PgpKeyHelper.convertKeyIdToHex(key.getKeyID()) }, indent); + indent -= 1; + continue; + } + + // re-add certification + modified = PGPPublicKey.addCertification(modified, selfCert); + // add revocation, if any + if (revocation != null) { + modified = PGPPublicKey.addCertification(modified, revocation); + } + // replace pubkey in keyring + ring = replacePublicKey(ring, modified); + indent -= 1; + } + + if (badCerts > 0 && redundantCerts > 0) { + log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_BAD_AND_RED, + new String[] { Integer.toString(badCerts), + Integer.toString(redundantCerts) }, indent); + } else if (badCerts > 0) { + log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_BAD, + new String[] { Integer.toString(badCerts) }, indent); + } else if (redundantCerts > 0) { + log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_REDUNDANT, + new String[] { Integer.toString(redundantCerts) }, indent); + } else { + log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS, null, indent); + } + + return new UncachedKeyRing(ring, true); + } + + /** This operation merges information from a different keyring, returning a combined + * UncachedKeyRing. + * + * The combined keyring contains the subkeys and user ids of both input keyrings, but it does + * not necessarily have the canonicalized property. + * + * @param other The UncachedKeyRing to merge. Must not be empty, and of the same masterKeyId + * @return A consolidated UncachedKeyRing with the data of both input keyrings. Same type as + * this object, or null on error. + * + */ + public UncachedKeyRing merge(UncachedKeyRing other, OperationLog log, int indent) { + + log.add(LogLevel.DEBUG, isSecret() ? LogType.MSG_MG_SECRET : LogType.MSG_MG_PUBLIC, + new String[]{ PgpKeyHelper.convertKeyIdToHex(getMasterKeyId()) }, indent); + indent += 1; + + long masterKeyId = other.getMasterKeyId(); + + if (getMasterKeyId() != masterKeyId) { + log.add(LogLevel.ERROR, LogType.MSG_MG_HETEROGENEOUS, null, indent); + return null; + } + + // remember which certs we already added. this is cheaper than semantic deduplication + Set<byte[]> certs = new TreeSet<byte[]>(new Comparator<byte[]>() { + public int compare(byte[] left, byte[] right) { + // check for length equality + if (left.length != right.length) { + return left.length - right.length; + } + // compare byte-by-byte + for (int i = 0; i < left.length && i < right.length; i++) { + if (left[i] != right[i]) { + return (left[i] & 0xff) - (right[i] & 0xff); + } + } + // ok they're the same + return 0; + }}); + + try { + PGPKeyRing result = mRing; + PGPKeyRing candidate = other.mRing; + + // Pre-load all existing certificates + for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(result.getPublicKeys())) { + for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) { + certs.add(cert.getEncoded()); + } + } + + // keep track of the number of new certs we add + int newCerts = 0; + + for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(candidate.getPublicKeys())) { + + final PGPPublicKey resultKey = result.getPublicKey(key.getKeyID()); + if (resultKey == null) { + log.add(LogLevel.DEBUG, LogType.MSG_MG_NEW_SUBKEY, null, indent); + result = replacePublicKey(result, key); + continue; + } + + // Modifiable version of the old key, which we merge stuff into (keep old for comparison) + PGPPublicKey modified = resultKey; + + // Iterate certifications + for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) { + int type = cert.getSignatureType(); + // Disregard certifications on user ids, we will deal with those later + if (type == PGPSignature.NO_CERTIFICATION + || type == PGPSignature.DEFAULT_CERTIFICATION + || type == PGPSignature.CASUAL_CERTIFICATION + || type == PGPSignature.POSITIVE_CERTIFICATION + || type == PGPSignature.CERTIFICATION_REVOCATION) { + continue; + } + + // Don't merge foreign stuff into secret keys + if (cert.getKeyID() != masterKeyId && isSecret()) { + continue; + } + + byte[] encoded = cert.getEncoded(); + // Known cert, skip it + if (certs.contains(encoded)) { + continue; + } + certs.add(encoded); + modified = PGPPublicKey.addCertification(modified, cert); + newCerts += 1; + } + + // If this is a subkey, merge it in and stop here + if (!key.isMasterKey()) { + if (modified != resultKey) { + result = replacePublicKey(result, modified); + } + continue; + } + + // Copy over all user id certificates + for (String userId : new IterableIterator<String>(key.getUserIDs())) { + for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignaturesForID(userId))) { + // Don't merge foreign stuff into secret keys + if (cert.getKeyID() != masterKeyId && isSecret()) { + continue; + } + byte[] encoded = cert.getEncoded(); + // Known cert, skip it + if (certs.contains(encoded)) { + continue; + } + newCerts += 1; + certs.add(encoded); + modified = PGPPublicKey.addCertification(modified, userId, cert); + } + } + // If anything changed, save the updated (sub)key + if (modified != resultKey) { + result = replacePublicKey(result, modified); + } + + } + + log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW, + new String[] { Integer.toString(newCerts) }, indent); + + return new UncachedKeyRing(result); + + } catch (IOException e) { + log.add(LogLevel.ERROR, LogType.MSG_MG_FATAL_ENCODE, null, indent); + return null; + } + + } + + /** This method replaces a public key in a keyring. + * + * This method essentially wraps PGP*KeyRing.insertPublicKey, where the keyring may be of either + * the secret or public subclass. + * + * @return the resulting PGPKeyRing of the same type as the input + */ + private static PGPKeyRing replacePublicKey(PGPKeyRing ring, PGPPublicKey key) { + if (ring instanceof PGPPublicKeyRing) { + return PGPPublicKeyRing.insertPublicKey((PGPPublicKeyRing) ring, key); + } + PGPSecretKeyRing secRing = (PGPSecretKeyRing) ring; + PGPSecretKey sKey = secRing.getSecretKey(key.getKeyID()); + // TODO generate secret key with S2K dummy, if none exists! for now, just die. + if (sKey == null) { + throw new RuntimeException("dummy secret key generation not yet implemented"); + } + sKey = PGPSecretKey.replacePublicKey(sKey, key); + return PGPSecretKeyRing.insertSecretKey(secRing, sKey); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 108c8c8c3..33db7771b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -2,6 +2,7 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.SignatureSubpacketTags; import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureSubpacketVector; @@ -9,6 +10,7 @@ import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProv import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.IterableIterator; +import java.security.SignatureException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -28,8 +30,13 @@ public class UncachedPublicKey { } /** The revocation signature is NOT checked here, so this may be false! */ - public boolean maybeRevoked() { - return mPublicKey.isRevoked(); + public boolean isRevoked() { + for (PGPSignature sig : new IterableIterator<PGPSignature>( + mPublicKey.getSignaturesOfType(isMasterKey() ? PGPSignature.KEY_REVOCATION + : PGPSignature.SUBKEY_REVOCATION))) { + return true; + } + return false; } public Date getCreationTime() { @@ -193,4 +200,5 @@ public class UncachedPublicKey { } }; } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java index 71d237c05..6f3068261 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java @@ -91,8 +91,18 @@ public abstract class WrappedKeyRing extends KeyRing { getRing().encode(stream); } + /** Returns an UncachedKeyRing which wraps the same data as this ring. This method should + * only be used */ + public UncachedKeyRing getUncachedKeyRing() { + return new UncachedKeyRing(getRing()); + } + abstract PGPKeyRing getRing(); abstract public IterableIterator<WrappedPublicKey> publicKeyIterator(); + public UncachedKeyRing getUncached() { + return new UncachedKeyRing(getRing()); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java index 9591cf8bc..d7148f710 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java @@ -154,8 +154,4 @@ public class WrappedSecretKeyRing extends WrappedKeyRing { }); } - public UncachedKeyRing getUncached() { - return new UncachedKeyRing(mRing); - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java index 1b7a5e8ba..196ac1dee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -35,7 +35,7 @@ public class WrappedSignature { final PGPSignature mSig; - protected WrappedSignature(PGPSignature sig) { + WrappedSignature(PGPSignature sig) { mSig = sig; } @@ -88,7 +88,7 @@ public class WrappedSignature { init(key.getPublicKey()); } - protected void init(PGPPublicKey key) throws PgpGeneralException { + void init(PGPPublicKey key) throws PgpGeneralException { try { JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() @@ -125,7 +125,27 @@ public class WrappedSignature { } } - protected boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException { + boolean verifySignature(PGPPublicKey key) throws PgpGeneralException { + try { + return mSig.verifyCertification(key); + } catch (SignatureException e) { + throw new PgpGeneralException("Sign!", e); + } catch (PGPException e) { + throw new PgpGeneralException("Error!", e); + } + } + + boolean verifySignature(PGPPublicKey masterKey, PGPPublicKey subKey) throws PgpGeneralException { + try { + return mSig.verifyCertification(masterKey, subKey); + } catch (SignatureException e) { + throw new PgpGeneralException("Sign!", e); + } catch (PGPException e) { + throw new PgpGeneralException("Error!", e); + } + } + + boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException { try { return mSig.verifyCertification(uid, key); } catch (SignatureException e) { @@ -158,4 +178,12 @@ public class WrappedSignature { return new WrappedSignature(signatures.get(0)); } + public boolean isLocal() { + if (!mSig.hasSubpackets() + || !mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.EXPORTABLE)) { + return false; + } + SignatureSubpacket p = mSig.getHashedSubPackets().getSubpacket(SignatureSubpacketTags.EXPORTABLE); + return p.getData()[0] == 0; + } } 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 5c8bf6752..79bd5777c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -29,9 +29,11 @@ import android.support.v4.util.LongSparseArray; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.OperationResultParcel; -import org.sufficientlysecure.keychain.pgp.OperationResultParcel.LogType; -import org.sufficientlysecure.keychain.pgp.OperationResultParcel.LogLevel; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.pgp.WrappedPublicKey; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; +import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; @@ -48,12 +50,14 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -61,18 +65,27 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +/** This class contains high level methods for database access. Despite its + * name, it is not only a helper but actually the main interface for all + * synchronous database operations. + * + * Operations in this class write logs. These can be obtained from the + * OperationResultParcel return values directly, but are also accumulated over + * the lifetime of the executing ProviderHelper object unless the resetLog() + * method is called to start a new one specifically. + * + */ public class ProviderHelper { private final Context mContext; private final ContentResolver mContentResolver; - private final ArrayList<OperationResultParcel.LogEntryParcel> mLog; + private OperationLog mLog; private int mIndent; public ProviderHelper(Context context) { - this(context, new ArrayList<OperationResultParcel.LogEntryParcel>(), 0); + this(context, new OperationLog(), 0); } - public ProviderHelper(Context context, ArrayList<OperationResultParcel.LogEntryParcel> log, - int indent) { + public ProviderHelper(Context context, OperationLog log, int indent) { mContext = context; mContentResolver = context.getContentResolver(); mLog = log; @@ -81,11 +94,16 @@ public class ProviderHelper { public void resetLog() { if(mLog != null) { - mLog.clear(); + // Start a new log (leaving the old one intact) + mLog = new OperationLog(); mIndent = 0; } } + public OperationLog getLog() { + return mLog; + } + public static class NotFoundException extends Exception { public NotFoundException() { } @@ -97,12 +115,12 @@ public class ProviderHelper { public void log(LogLevel level, LogType type) { if(mLog != null) { - mLog.add(new OperationResultParcel.LogEntryParcel(level, type, null, mIndent)); + mLog.add(level, type, null, mIndent); } } public void log(LogLevel level, LogType type, String[] parameters) { if(mLog != null) { - mLog.add(new OperationResultParcel.LogEntryParcel(level, type, parameters, mIndent)); + mLog.add(level, type, parameters, mIndent); } } @@ -156,45 +174,42 @@ public class ProviderHelper { } } - public Object getUnifiedData(long masterKeyId, String column, int type) - throws NotFoundException { - return getUnifiedData(masterKeyId, new String[]{column}, new int[]{type}).get(column); - } - public HashMap<String, Object> getUnifiedData(long masterKeyId, String[] proj, int[] types) throws NotFoundException { return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types); } - private LongSparseArray<UncachedPublicKey> getUncachedMasterKeys(Uri queryUri) { - Cursor cursor = mContentResolver.query(queryUri, - new String[]{KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA}, - null, null, null); + private LongSparseArray<WrappedPublicKey> getTrustedMasterKeys() { + Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[] { + KeyRings.MASTER_KEY_ID, + // we pick from cache only information that is not easily available from keyrings + KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED, + // and of course, ring data + KeyRings.PUBKEY_DATA + }, KeyRings.HAS_ANY_SECRET + " = 1", null, null); - LongSparseArray<UncachedPublicKey> result = - new LongSparseArray<UncachedPublicKey>(cursor.getCount()); try { + LongSparseArray<WrappedPublicKey> result = new LongSparseArray<WrappedPublicKey>(); + if (cursor != null && cursor.moveToFirst()) do { long masterKeyId = cursor.getLong(0); - byte[] data = cursor.getBlob(1); - if (data != null) { - try { - result.put(masterKeyId, - UncachedKeyRing.decodeFromData(data).getPublicKey()); - } catch(PgpGeneralException e) { - Log.e(Constants.TAG, "Error parsing keyring, skipping " + masterKeyId, e); - } catch(IOException e) { - Log.e(Constants.TAG, "IO error, skipping keyring" + masterKeyId, e); - } + boolean hasAnySecret = cursor.getInt(1) > 0; + int verified = cursor.getInt(2); + byte[] blob = cursor.getBlob(3); + if (blob != null) { + result.put(masterKeyId, + new WrappedPublicKeyRing(blob, hasAnySecret, verified).getSubkey()); } } while (cursor.moveToNext()); + + return result; + } finally { if (cursor != null) { cursor.close(); } } - return result; } public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) { @@ -236,7 +251,7 @@ public class ProviderHelper { throw new NotFoundException("Secret key not available!"); } return secret - ? new WrappedSecretKeyRing(blob, hasAnySecret, verified) + ? new WrappedSecretKeyRing(blob, true, verified) : new WrappedPublicKeyRing(blob, hasAnySecret, verified); } else { throw new NotFoundException("Key not found!"); @@ -248,90 +263,150 @@ public class ProviderHelper { } } - /** - * Saves PGPPublicKeyRing with its keys and userIds in DB + /** Saves an UncachedKeyRing of the public variant into the db. + * + * This method will not delete all previous data for this masterKeyId from the database prior + * to inserting. All public data is effectively re-inserted, secret keyrings are left deleted + * and need to be saved externally to be preserved past the operation. */ @SuppressWarnings("unchecked") - public OperationResultParcel savePublicKeyRing(UncachedKeyRing keyRing) { + private int internalSavePublicKeyRing(UncachedKeyRing keyRing, + Progressable progress, boolean selfCertsAreTrusted) { if (keyRing.isSecret()) { log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET); - return new OperationResultParcel(1, mLog); + return SaveKeyringResult.RESULT_ERROR; } - - UncachedPublicKey masterKey = keyRing.getPublicKey(); - long masterKeyId = masterKey.getKeyId(); - log(LogLevel.INFO, LogType.MSG_IP_IMPORTING, - new String[]{Long.toString(masterKeyId)}); - mIndent += 1; - - // IF there is a secret key, preserve it! - UncachedKeyRing secretRing; - try { - secretRing = getWrappedSecretKeyRing(masterKeyId).getUncached(); - log(LogLevel.DEBUG, LogType.MSG_IP_PRESERVING_SECRET); - } catch (NotFoundException e) { - secretRing = null; + if (!keyRing.isCanonicalized()) { + log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET); + return SaveKeyringResult.RESULT_ERROR; } - // delete old version of this keyRing, which also deletes all keys and userIds on cascade - try { - mContentResolver.delete(KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null); - log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_OK); - } catch (UnsupportedOperationException e) { - Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e); - log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_FAIL); - } + // start with ok result + int result = SaveKeyringResult.SAVED_PUBLIC; - // insert new version of this keyRing - ContentValues values = new ContentValues(); - values.put(KeyRingData.MASTER_KEY_ID, masterKeyId); + long masterKeyId = keyRing.getMasterKeyId(); + UncachedPublicKey masterKey = keyRing.getPublicKey(); + + ArrayList<ContentProviderOperation> operations; try { - values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded()); - } catch (IOException e) { - log(LogLevel.ERROR, LogType.MSG_IP_ENCODE_FAIL); - return new OperationResultParcel(1, mLog); - } - // save all keys and userIds included in keyRing object in database - ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); + log(LogLevel.DEBUG, LogType.MSG_IP_PREPARE); + mIndent += 1; - try { + // save all keys and userIds included in keyRing object in database + operations = new ArrayList<ContentProviderOperation>(); log(LogLevel.INFO, LogType.MSG_IP_INSERT_KEYRING); - Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)); - operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build()); + { // insert keyring + ContentValues values = new ContentValues(); + values.put(KeyRingData.MASTER_KEY_ID, masterKeyId); + try { + values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded()); + } catch (IOException e) { + log(LogLevel.ERROR, LogType.MSG_IP_ENCODE_FAIL); + return SaveKeyringResult.RESULT_ERROR; + } + + Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)); + operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build()); + } log(LogLevel.INFO, LogType.MSG_IP_INSERT_SUBKEYS); + progress.setProgress(LogType.MSG_IP_INSERT_SUBKEYS.getMsgId(), 40, 100); mIndent += 1; - int rank = 0; - for (UncachedPublicKey key : new IterableIterator<UncachedPublicKey>(keyRing.getPublicKeys())) { - log(LogLevel.DEBUG, LogType.MSG_IP_INSERT_SUBKEY, new String[] { - PgpKeyHelper.convertKeyIdToHex(key.getKeyId()) - }); - operations.add(buildPublicKeyOperations(masterKeyId, key, rank)); - ++rank; + { // insert subkeys + Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId)); + int rank = 0; + for (UncachedPublicKey key : new IterableIterator<UncachedPublicKey>(keyRing.getPublicKeys())) { + long keyId = key.getKeyId(); + log(LogLevel.DEBUG, keyId == masterKeyId ? LogType.MSG_IP_MASTER : LogType.MSG_IP_SUBKEY, new String[]{ + PgpKeyHelper.convertKeyIdToHex(keyId) + }); + mIndent += 1; + + ContentValues values = new ContentValues(); + values.put(Keys.MASTER_KEY_ID, masterKeyId); + values.put(Keys.RANK, rank); + + values.put(Keys.KEY_ID, key.getKeyId()); + values.put(Keys.KEY_SIZE, key.getBitStrength()); + values.put(Keys.ALGORITHM, key.getAlgorithm()); + values.put(Keys.FINGERPRINT, key.getFingerprint()); + + boolean c = key.canCertify(), e = key.canEncrypt(), s = key.canSign(); + values.put(Keys.CAN_CERTIFY, c); + values.put(Keys.CAN_ENCRYPT, e); + values.put(Keys.CAN_SIGN, s); + values.put(Keys.IS_REVOKED, key.isRevoked()); + if (masterKeyId == keyId) { + if (c) { + if (e) { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_CES + : LogType.MSG_IP_MASTER_FLAGS_CEX, null); + } else { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_CXS + : LogType.MSG_IP_MASTER_FLAGS_CXX, null); + } + } else { + if (e) { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_XES + : LogType.MSG_IP_MASTER_FLAGS_XEX, null); + } else { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_XXS + : LogType.MSG_IP_MASTER_FLAGS_XXX, null); + } + } + } else { + if (c) { + if (e) { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_CES + : LogType.MSG_IP_SUBKEY_FLAGS_CEX, null); + } else { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_CXS + : LogType.MSG_IP_SUBKEY_FLAGS_CXX, null); + } + } else { + if (e) { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_XES + : LogType.MSG_IP_SUBKEY_FLAGS_XEX, null); + } else { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_XXS + : LogType.MSG_IP_SUBKEY_FLAGS_XXX, null); + } + } + } + + Date creation = key.getCreationTime(); + values.put(Keys.CREATION, creation.getTime() / 1000); + Date expiryDate = key.getExpiryTime(); + if (expiryDate != null) { + values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); + if (key.isExpired()) { + log(LogLevel.DEBUG, keyId == masterKeyId ? + LogType.MSG_IP_MASTER_EXPIRED : LogType.MSG_IP_SUBKEY_EXPIRED, + new String[]{ expiryDate.toString() }); + } else { + log(LogLevel.DEBUG, keyId == masterKeyId ? + LogType.MSG_IP_MASTER_EXPIRES : LogType.MSG_IP_SUBKEY_EXPIRES, + new String[] { expiryDate.toString() }); + } + } + + operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build()); + ++rank; + mIndent -= 1; + } } mIndent -= 1; - log(LogLevel.DEBUG, LogType.MSG_IP_TRUST_RETRIEVE); // get a list of owned secret keys, for verification filtering - LongSparseArray<UncachedPublicKey> trustedKeys = - getUncachedMasterKeys(KeyRingData.buildSecretKeyRingUri()); - // special case: available secret keys verify themselves! - if (secretRing != null) { - trustedKeys.put(secretRing.getMasterKeyId(), secretRing.getPublicKey()); - log(LogLevel.INFO, LogType.MSG_IP_TRUST_USING_SEC, new String[]{ - Integer.toString(trustedKeys.size()) - }); - } else { - log(LogLevel.INFO, LogType.MSG_IP_TRUST_USING, new String[] { - Integer.toString(trustedKeys.size()) - }); - } + LongSparseArray<WrappedPublicKey> trustedKeys = getTrustedMasterKeys(); // classify and order user ids. primary are moved to the front, revoked to the back, // otherwise the order in the keyfile is preserved. - log(LogLevel.DEBUG, LogType.MSG_IP_UID_CLASSIFYING); + log(LogLevel.INFO, LogType.MSG_IP_UID_CLASSIFYING, new String[]{ + Integer.toString(trustedKeys.size()) + }); mIndent += 1; List<UserIdItem> uids = new ArrayList<UserIdItem>(); for (String userId : new IterableIterator<String>( @@ -342,7 +417,7 @@ public class ProviderHelper { int unknownCerts = 0; - log(LogLevel.INFO, LogType.MSG_IP_UID_PROCESSING, new String[] { userId }); + log(LogLevel.INFO, LogType.MSG_IP_UID_PROCESSING, new String[]{ userId }); mIndent += 1; // look through signatures for this specific key for (WrappedSignature cert : new IterableIterator<WrappedSignature>( @@ -351,41 +426,29 @@ public class ProviderHelper { try { // self signature if (certId == masterKeyId) { - cert.init(masterKey); - if (!cert.verifySignature(masterKey, userId)) { - // Bad self certification? That's kinda bad... - log(LogLevel.ERROR, LogType.MSG_IP_UID_SELF_BAD); - return new OperationResultParcel(1, mLog); - } - // if we already have a cert.. - if (item.selfCert != null) { - // ..is this perchance a more recent one? - if (item.selfCert.getCreationTime().before(cert.getCreationTime())) { - log(LogLevel.DEBUG, LogType.MSG_IP_UID_SELF_NEWER); - } else { - log(LogLevel.DEBUG, LogType.MSG_IP_UID_SELF_IGNORING_OLD); - continue; - } + // NOTE self-certificates are already verified during canonicalization, + // AND we know there is at most one cert plus at most one revocation + if (!cert.isRevocation()) { + item.selfCert = cert; + item.isPrimary = cert.isPrimaryUserId(); } else { - log(LogLevel.DEBUG, LogType.MSG_IP_UID_SELF_GOOD); + item.isRevoked = true; + log(LogLevel.INFO, LogType.MSG_IP_UID_REVOKED); } - - // save certificate as primary self-cert - item.selfCert = cert; - item.isPrimary = cert.isPrimaryUserId(); - item.isRevoked = cert.isRevocation(); + continue; } // verify signatures from known private keys if (trustedKeys.indexOfKey(certId) >= 0) { - UncachedPublicKey trustedKey = trustedKeys.get(certId); + WrappedPublicKey trustedKey = trustedKeys.get(certId); cert.init(trustedKey); if (cert.verifySignature(masterKey, userId)) { item.trustedCerts.add(cert); log(LogLevel.INFO, LogType.MSG_IP_UID_CERT_GOOD, new String[] { - PgpKeyHelper.convertKeyIdToHex(trustedKey.getKeyId()) + PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId()), + trustedKey.getPrimaryUserId() }); } else { log(LogLevel.WARN, LogType.MSG_IP_UID_CERT_BAD); @@ -400,18 +463,19 @@ public class ProviderHelper { }); } } - mIndent -= 1; if (unknownCerts > 0) { - log(LogLevel.DEBUG, LogType.MSG_IP_UID_CERTS_UNKNOWN, new String[] { + log(LogLevel.DEBUG, LogType.MSG_IP_UID_CERTS_UNKNOWN, new String[]{ Integer.toString(unknownCerts) }); } + mIndent -= 1; } mIndent -= 1; - log(LogLevel.INFO, LogType.MSG_IP_UID_INSERT); + progress.setProgress(LogType.MSG_IP_UID_REORDER.getMsgId(), 65, 100); + log(LogLevel.DEBUG, LogType.MSG_IP_UID_REORDER); // primary before regular before revoked (see UserIdItem.compareTo) // this is a stable sort, so the order of keys is otherwise preserved. Collections.sort(uids); @@ -419,10 +483,9 @@ public class ProviderHelper { for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) { UserIdItem item = uids.get(userIdRank); operations.add(buildUserIdOperations(masterKeyId, item, userIdRank)); - // no self cert is bad, but allowed by the rfc... if (item.selfCert != null) { - operations.add(buildCertOperations( - masterKeyId, userIdRank, item.selfCert, Certs.VERIFIED_SELF)); + operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert, + selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF)); } // don't bother with trusted certs if the uid is revoked, anyways if (item.isRevoked) { @@ -434,37 +497,47 @@ public class ProviderHelper { } } - log(LogLevel.DEBUG, LogType.MSG_IP_APPLY_BATCH); - mContentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); + mIndent -= 1; + } catch (IOException e) { log(LogLevel.ERROR, LogType.MSG_IP_FAIL_IO_EXC); Log.e(Constants.TAG, "IOException during import", e); mIndent -= 1; - return new OperationResultParcel(1, mLog); + return SaveKeyringResult.RESULT_ERROR; + } + + try { + // delete old version of this keyRing, which also deletes all keys and userIds on cascade + int deleted = mContentResolver.delete( + KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null); + if (deleted > 0) { + log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_OK); + result |= SaveKeyringResult.UPDATED; + } else { + log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_FAIL); + } + + log(LogLevel.DEBUG, LogType.MSG_IP_APPLY_BATCH); + progress.setProgress(LogType.MSG_IP_APPLY_BATCH.getMsgId(), 75, 100); + mContentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); + + log(LogLevel.OK, LogType.MSG_IP_SUCCESS); + mIndent -= 1; + progress.setProgress(LogType.MSG_IP_SUCCESS.getMsgId(), 90, 100); + return result; + } catch (RemoteException e) { log(LogLevel.ERROR, LogType.MSG_IP_FAIL_REMOTE_EX); Log.e(Constants.TAG, "RemoteException during import", e); mIndent -= 1; - return new OperationResultParcel(1, mLog); + return SaveKeyringResult.RESULT_ERROR; } catch (OperationApplicationException e) { - log(LogLevel.ERROR, LogType.MSG_IP_FAIL_OP_EX); + log(LogLevel.ERROR, LogType.MSG_IP_FAIL_OP_EXC); Log.e(Constants.TAG, "OperationApplicationException during import", e); mIndent -= 1; - return new OperationResultParcel(1, mLog); + return SaveKeyringResult.RESULT_ERROR; } - // Save the saved keyring (if any) - if (secretRing != null) { - log(LogLevel.DEBUG, LogType.MSG_IP_REINSERT_SECRET); - mIndent += 1; - saveSecretKeyRing(secretRing); - mIndent -= 1; - } - - log(LogLevel.INFO, LogType.MSG_IP_SUCCESS); - mIndent -= 1; - return new OperationResultParcel(0, mLog); - } private static class UserIdItem implements Comparable<UserIdItem> { @@ -488,19 +561,34 @@ public class ProviderHelper { } } - /** - * Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring - * is already in the database! + /** Saves an UncachedKeyRing of the secret variant into the db. + * This method will fail if no corresponding public keyring is in the database! */ - public OperationResultParcel saveSecretKeyRing(UncachedKeyRing keyRing) { + private int internalSaveSecretKeyRing(UncachedKeyRing keyRing) { + if (!keyRing.isSecret()) { log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC); - return new OperationResultParcel(1, mLog); + return SaveKeyringResult.RESULT_ERROR; + } + + if (!keyRing.isCanonicalized()) { + log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC); + return SaveKeyringResult.RESULT_ERROR; } long masterKeyId = keyRing.getMasterKeyId(); - log(LogLevel.INFO, LogType.MSG_IS_IMPORTING, - new String[]{ Long.toString(masterKeyId) }); + log(LogLevel.START, LogType.MSG_IS, + new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) }); + mIndent += 1; + + // Canonicalize this key, to assert a number of assumptions made about it. + keyRing = keyRing.canonicalize(mLog, mIndent); + if (keyRing == null) { + return SaveKeyringResult.RESULT_ERROR; + } + + // IF this is successful, it's a secret key + int result = SaveKeyringResult.SAVED_SECRET; // save secret keyring try { @@ -509,11 +597,14 @@ public class ProviderHelper { values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded()); // insert new version of this keyRing Uri uri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)); - mContentResolver.insert(uri, values); + if (mContentResolver.insert(uri, values) == null) { + log(LogLevel.ERROR, LogType.MSG_IS_DB_EXCEPTION); + return SaveKeyringResult.RESULT_ERROR; + } } catch (IOException e) { Log.e(Constants.TAG, "Failed to encode key!", e); - log(LogLevel.ERROR, LogType.MSG_IS_IO_EXCPTION); - return new OperationResultParcel(1, mLog); + log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC); + return SaveKeyringResult.RESULT_ERROR; } { @@ -556,54 +647,220 @@ public class ProviderHelper { // with has_secret = 0 } - log(LogLevel.INFO, LogType.MSG_IS_SUCCESS); - return new OperationResultParcel(0, mLog); + log(LogLevel.OK, LogType.MSG_IS_SUCCESS); + return result; } - /** - * Saves (or updates) a pair of public and secret KeyRings in the database - */ - public void saveKeyRing(UncachedKeyRing pubRing, UncachedKeyRing secRing) throws IOException { - long masterKeyId = pubRing.getPublicKey().getKeyId(); - // delete secret keyring (so it isn't unnecessarily saved by public-savePublicKeyRing below) - mContentResolver.delete(KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)), null, null); + @Deprecated + public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) { + return savePublicKeyRing(keyRing, new Progressable() { + @Override + public void setProgress(String message, int current, int total) { + } - // save public keyring - savePublicKeyRing(pubRing); - saveSecretKeyRing(secRing); + @Override + public void setProgress(int resourceId, int current, int total) { + } + + @Override + public void setProgress(int current, int total) { + } + }); } - /** - * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing + /** Save a public keyring into the database. + * + * This is a high level method, which takes care of merging all new information into the old and + * keep public and secret keyrings in sync. */ - private ContentProviderOperation - buildPublicKeyOperations(long masterKeyId, UncachedPublicKey key, int rank) throws IOException { + public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress) { - ContentValues values = new ContentValues(); - values.put(Keys.MASTER_KEY_ID, masterKeyId); - values.put(Keys.RANK, rank); - - values.put(Keys.KEY_ID, key.getKeyId()); - values.put(Keys.KEY_SIZE, key.getBitStrength()); - values.put(Keys.ALGORITHM, key.getAlgorithm()); - values.put(Keys.FINGERPRINT, key.getFingerprint()); - - values.put(Keys.CAN_CERTIFY, key.canCertify()); - values.put(Keys.CAN_SIGN, key.canSign()); - values.put(Keys.CAN_ENCRYPT, key.canEncrypt()); - values.put(Keys.IS_REVOKED, key.maybeRevoked()); - - values.put(Keys.CREATION, key.getCreationTime().getTime() / 1000); - Date expiryDate = key.getExpiryTime(); - if (expiryDate != null) { - values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); + try { + long masterKeyId = publicRing.getMasterKeyId(); + log(LogLevel.START, LogType.MSG_IP, + new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) }); + mIndent += 1; + + // If there is an old keyring, merge it + try { + UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached(); + + // Merge data from new public ring into the old one + publicRing = oldPublicRing.merge(publicRing, mLog, mIndent); + + // If this is null, there is an error in the log so we can just return + if (publicRing == null) { + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + + // Canonicalize this keyring, to assert a number of assumptions made about it. + publicRing = publicRing.canonicalize(mLog, mIndent); + if (publicRing == null) { + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + + // Early breakout if nothing changed + if (Arrays.hashCode(publicRing.getEncoded()) + == Arrays.hashCode(oldPublicRing.getEncoded())) { + log(LogLevel.OK, LogType.MSG_IP_SUCCESS_IDENTICAL, null); + return new SaveKeyringResult(SaveKeyringResult.RESULT_OK, mLog); + } + } catch (NotFoundException e) { + // Not an issue, just means we are dealing with a new keyring. + + // Canonicalize this keyring, to assert a number of assumptions made about it. + publicRing = publicRing.canonicalize(mLog, mIndent); + if (publicRing == null) { + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + + } + + // If there is a secret key, merge new data (if any) and save the key for later + UncachedKeyRing secretRing; + try { + secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncached(); + + // Merge data from new public ring into secret one + secretRing = secretRing.merge(publicRing, mLog, mIndent); + if (secretRing == null) { + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + secretRing = secretRing.canonicalize(mLog, mIndent); + if (secretRing == null) { + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + + } catch (NotFoundException e) { + // No secret key available (this is what happens most of the time) + secretRing = null; + } + + int result = internalSavePublicKeyRing(publicRing, progress, secretRing != null); + + // Save the saved keyring (if any) + if (secretRing != null) { + progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100); + int secretResult = internalSaveSecretKeyRing(secretRing); + if ((secretResult & SaveKeyringResult.RESULT_ERROR) != SaveKeyringResult.RESULT_ERROR) { + result |= SaveKeyringResult.SAVED_SECRET; + } + } + + mIndent -= 1; + return new SaveKeyringResult(result, mLog); + + } catch (IOException e) { + log(LogLevel.ERROR, LogType.MSG_IP_FAIL_IO_EXC); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); } - Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId)); + } + + public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing, Progressable progress) { + + try { + long masterKeyId = secretRing.getMasterKeyId(); + log(LogLevel.START, LogType.MSG_IS, + new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) }); + mIndent += 1; + + // If there is an old secret key, merge it. + try { + UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncached(); + + // Merge data from new secret ring into old one + secretRing = oldSecretRing.merge(secretRing, mLog, mIndent); + + // If this is null, there is an error in the log so we can just return + if (secretRing == null) { + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + + // Canonicalize this keyring, to assert a number of assumptions made about it. + secretRing = secretRing.canonicalize(mLog, mIndent); + if (secretRing == null) { + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + + // Early breakout if nothing changed + if (Arrays.hashCode(secretRing.getEncoded()) + == Arrays.hashCode(oldSecretRing.getEncoded())) { + log(LogLevel.OK, LogType.MSG_IS_SUCCESS_IDENTICAL, + new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) }); + return new SaveKeyringResult(SaveKeyringResult.RESULT_OK, mLog); + } + } catch (NotFoundException e) { + // Not an issue, just means we are dealing with a new keyring + + // Canonicalize this keyring, to assert a number of assumptions made about it. + secretRing = secretRing.canonicalize(mLog, mIndent); + if (secretRing == null) { + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + + } + + // Merge new data into public keyring as well, if there is any + try { + UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached(); + + // Merge data from new public ring into secret one + UncachedKeyRing publicRing = oldPublicRing.merge(secretRing, mLog, mIndent); + if (publicRing == null) { + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + + // If anything changed, reinsert + if (Arrays.hashCode(publicRing.getEncoded()) + != Arrays.hashCode(oldPublicRing.getEncoded())) { + + log(LogLevel.OK, LogType.MSG_IS, + new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) }); + + publicRing = publicRing.canonicalize(mLog, mIndent); + if (publicRing == null) { + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + + int result = internalSavePublicKeyRing(publicRing, progress, true); + if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) { + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + + } + + } catch (NotFoundException e) { + // TODO, this WILL error out later because secret rings cannot be inserted without + // public ones + } + + progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100); + int result = internalSaveSecretKeyRing(secretRing); + return new SaveKeyringResult(result, mLog); + + } catch (IOException e) { + log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC, null); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } - return ContentProviderOperation.newInsert(uri).withValues(values).build(); + } + + /** + * Saves (or updates) a pair of public and secret KeyRings in the database + */ + @Deprecated // scheduled for deletion after merge with new-edit branch + public void savePairedKeyRing(UncachedKeyRing pubRing, UncachedKeyRing secRing) throws IOException { + long masterKeyId = pubRing.getMasterKeyId(); + + // delete secret keyring (so it isn't unnecessarily saved by public-savePublicKeyRing below) + mContentResolver.delete(KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)), null, null); + + // save public keyring + internalSavePublicKeyRing(pubRing, null, true); + internalSaveSecretKeyRing(secRing); } /** @@ -719,9 +976,6 @@ public class ProviderHelper { /** * Must be an uri pointing to an account - * - * @param uri - * @return */ public AppSettings getApiAppSettings(Uri uri) { AppSettings settings = null; 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 27f41e3d2..5358f36e8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -175,14 +175,11 @@ public class KeychainIntentService extends IntentService public static final String RESULT_DECRYPTED_BYTES = "decrypted_data"; public static final String RESULT_DECRYPT_VERIFY_RESULT = "signature"; - // import - public static final String RESULT_IMPORT_ADDED = "added"; - public static final String RESULT_IMPORT_UPDATED = "updated"; - public static final String RESULT_IMPORT_BAD = "bad"; - // export public static final String RESULT_EXPORT = "exported"; + public static final String RESULT = "result"; + Messenger mMessenger; private boolean mIsCanceled; @@ -516,7 +513,7 @@ public class KeychainIntentService extends IntentService UncachedKeyRing newKeyRing = keyRing.changeSecretKeyPassphrase(oldPassphrase, newPassphrase); setProgress(R.string.progress_saving_key_ring, 50, 100); - providerHelper.saveSecretKeyRing(newKeyRing); + // providerHelper.saveSecretKeyRing(newKeyRing); setProgress(R.string.progress_done, 100, 100); } else { PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 90, 100)); @@ -527,13 +524,13 @@ public class KeychainIntentService extends IntentService PgpKeyOperation.Pair<UncachedKeyRing,UncachedKeyRing> pair = keyOperations.buildSecretKey(seckey, pubkey, saveParcel); // edit existing setProgress(R.string.progress_saving_key_ring, 90, 100); - providerHelper.saveKeyRing(pair.first, pair.second); + providerHelper.savePairedKeyRing(pair.first, pair.second); } catch (ProviderHelper.NotFoundException e) { PgpKeyOperation.Pair<UncachedKeyRing,UncachedKeyRing> pair = keyOperations.buildNewSecretKey(saveParcel); //new Keyring // save the pair setProgress(R.string.progress_saving_key_ring, 90, 100); - providerHelper.saveKeyRing(pair.first, pair.second); + providerHelper.savePairedKeyRing(pair.first, pair.second); } setProgress(R.string.progress_done, 100, 100); @@ -649,7 +646,10 @@ public class KeychainIntentService extends IntentService List<ParcelableKeyRing> entries = data.getParcelableArrayList(IMPORT_KEY_LIST); PgpImportExport pgpImportExport = new PgpImportExport(this, this); - Bundle resultData = pgpImportExport.importKeyRings(entries); + OperationResults.ImportResult result = pgpImportExport.importKeyRings(entries); + + Bundle resultData = new Bundle(); + resultData.putParcelable(RESULT, result); sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); } catch (Exception e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java new file mode 100644 index 000000000..48eb39a39 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java @@ -0,0 +1,293 @@ +package org.sufficientlysecure.keychain.service; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.IterableIterator; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; + +/** Represent the result of an operation. + * + * This class holds a result and the log of an operation. It can be subclassed + * to include typed additional information specific to the operation. To keep + * the class structure (somewhat) simple, this class contains an exhaustive + * list (ie, enum) of all possible log types, which should in all cases be tied + * to string resource ids. + * + * + */ +public class OperationResultParcel implements Parcelable { + /** Holds the overall result, the number specifying varying degrees of success. The first bit + * is 0 on overall success, 1 on overall failure. All other bits may be used for more specific + * conditions. */ + final int mResult; + + public static final int RESULT_OK = 0; + public static final int RESULT_ERROR = 1; + + /// A list of log entries tied to the operation result. + final OperationLog mLog; + + public OperationResultParcel(int result, OperationLog log) { + mResult = result; + mLog = log; + } + + public OperationResultParcel(Parcel source) { + mResult = source.readInt(); + mLog = new OperationLog(); + mLog.addAll(source.createTypedArrayList(LogEntryParcel.CREATOR)); + } + + public int getResult() { + return mResult; + } + + public boolean success() { + return (mResult & 1) == 0; + } + + public OperationLog getLog() { + return mLog; + } + + /** One entry in the log. */ + public static class LogEntryParcel implements Parcelable { + public final LogLevel mLevel; + public final LogType mType; + public final String[] mParameters; + public final int mIndent; + + public LogEntryParcel(LogLevel level, LogType type, String[] parameters, int indent) { + mLevel = level; + mType = type; + mParameters = parameters; + mIndent = indent; + } + public LogEntryParcel(LogLevel level, LogType type, String[] parameters) { + this(level, type, parameters, 0); + } + + public LogEntryParcel(Parcel source) { + mLevel = LogLevel.values()[source.readInt()]; + mType = LogType.values()[source.readInt()]; + mParameters = source.createStringArray(); + mIndent = source.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mLevel.ordinal()); + dest.writeInt(mType.ordinal()); + dest.writeStringArray(mParameters); + dest.writeInt(mIndent); + } + + public static final Creator<LogEntryParcel> CREATOR = new Creator<LogEntryParcel>() { + public LogEntryParcel createFromParcel(final Parcel source) { + return new LogEntryParcel(source); + } + + public LogEntryParcel[] newArray(final int size) { + return new LogEntryParcel[size]; + } + }; + + } + + /** This is an enum of all possible log events. + * + * Element names should generally be prefixed with MSG_XX_ where XX is an + * identifier based on the related activity. + * + * Log messages should occur for each distinguishable action group. For + * each such group, one message is displayed followed by warnings or + * errors, and optionally subactions. The granularity should generally be + * optimistic: No "success" messages are printed except for the outermost + * operations - the success of an action group is indicated by the + * beginning message of the next action group. + * + * Log messages should be in present tense, There should be no trailing + * punctuation, except for error messages which may end in an exclamation + * mark. + * + */ + public static enum LogType { + + // import public + MSG_IP(R.string.msg_ip), + MSG_IP_APPLY_BATCH (R.string.msg_ip_apply_batch), + MSG_IP_BAD_TYPE_SECRET (R.string.msg_ip_bad_type_secret), + MSG_IP_DELETE_OLD_FAIL (R.string.msg_ip_delete_old_fail), + MSG_IP_DELETE_OLD_OK (R.string.msg_ip_delete_old_ok), + MSG_IP_ENCODE_FAIL (R.string.msg_ip_encode_fail), + MSG_IP_FAIL_IO_EXC (R.string.msg_ip_fail_io_exc), + MSG_IP_FAIL_OP_EXC (R.string.msg_ip_fail_op_exc), + MSG_IP_FAIL_REMOTE_EX (R.string.msg_ip_fail_remote_ex), + MSG_IP_INSERT_KEYRING (R.string.msg_ip_insert_keyring), + MSG_IP_INSERT_SUBKEYS (R.string.msg_ip_insert_keys), + MSG_IP_PREPARE (R.string.msg_ip_prepare), + MSG_IP_REINSERT_SECRET (R.string.msg_ip_reinsert_secret), + MSG_IP_MASTER (R.string.msg_ip_master), + MSG_IP_MASTER_EXPIRED (R.string.msg_ip_master_expired), + MSG_IP_MASTER_EXPIRES (R.string.msg_ip_master_expires), + MSG_IP_MASTER_FLAGS_CES (R.string.msg_ip_master_flags_ces), + MSG_IP_MASTER_FLAGS_CEX (R.string.msg_ip_master_flags_cex), + MSG_IP_MASTER_FLAGS_CXS (R.string.msg_ip_master_flags_cxs), + MSG_IP_MASTER_FLAGS_XES (R.string.msg_ip_master_flags_xes), + MSG_IP_MASTER_FLAGS_CXX (R.string.msg_ip_master_flags_cxx), + MSG_IP_MASTER_FLAGS_XEX (R.string.msg_ip_master_flags_xex), + MSG_IP_MASTER_FLAGS_XXS (R.string.msg_ip_master_flags_xxs), + MSG_IP_MASTER_FLAGS_XXX (R.string.msg_ip_master_flags_xxx), + MSG_IP_SUBKEY (R.string.msg_ip_subkey), + MSG_IP_SUBKEY_EXPIRED (R.string.msg_ip_subkey_expired), + MSG_IP_SUBKEY_EXPIRES (R.string.msg_ip_subkey_expires), + MSG_IP_SUBKEY_FLAGS_CES (R.string.msg_ip_subkey_flags_ces), + MSG_IP_SUBKEY_FLAGS_CEX (R.string.msg_ip_subkey_flags_cex), + MSG_IP_SUBKEY_FLAGS_CXS (R.string.msg_ip_subkey_flags_cxs), + MSG_IP_SUBKEY_FLAGS_XES (R.string.msg_ip_subkey_flags_xes), + MSG_IP_SUBKEY_FLAGS_CXX (R.string.msg_ip_subkey_flags_cxx), + MSG_IP_SUBKEY_FLAGS_XEX (R.string.msg_ip_subkey_flags_xex), + MSG_IP_SUBKEY_FLAGS_XXS (R.string.msg_ip_subkey_flags_xxs), + MSG_IP_SUBKEY_FLAGS_XXX (R.string.msg_ip_subkey_flags_xxx), + MSG_IP_SUCCESS (R.string.msg_ip_success), + MSG_IP_SUCCESS_IDENTICAL (R.string.msg_ip_success_identical), + MSG_IP_UID_CERT_BAD (R.string.msg_ip_uid_cert_bad), + MSG_IP_UID_CERT_ERROR (R.string.msg_ip_uid_cert_error), + MSG_IP_UID_CERT_GOOD (R.string.msg_ip_uid_cert_good), + MSG_IP_UID_CERTS_UNKNOWN (R.string.msg_ip_uid_certs_unknown), + MSG_IP_UID_CLASSIFYING (R.string.msg_ip_uid_classifying), + MSG_IP_UID_REORDER(R.string.msg_ip_uid_reorder), + MSG_IP_UID_PROCESSING (R.string.msg_ip_uid_processing), + MSG_IP_UID_REVOKED (R.string.msg_ip_uid_revoked), + + // import secret + MSG_IS(R.string.msg_is), + MSG_IS_BAD_TYPE_PUBLIC (R.string.msg_is_bad_type_public), + MSG_IS_DB_EXCEPTION (R.string.msg_is_db_exception), + MSG_IS_IMPORTING_SUBKEYS (R.string.msg_is_importing_subkeys), + MSG_IS_FAIL_IO_EXC (R.string.msg_is_io_exc), + MSG_IS_SUBKEY_NONEXISTENT (R.string.msg_is_subkey_nonexistent), + MSG_IS_SUBKEY_OK (R.string.msg_is_subkey_ok), + MSG_IS_SUBKEY_STRIPPED (R.string.msg_is_subkey_stripped), + MSG_IS_SUCCESS (R.string.msg_is_success), + MSG_IS_SUCCESS_IDENTICAL (R.string.msg_is_success_identical), + + // keyring canonicalization + MSG_KC_PUBLIC (R.string.msg_kc_public), + MSG_KC_SECRET (R.string.msg_kc_secret), + MSG_KC_FATAL_NO_UID (R.string.msg_kc_fatal_no_uid), + MSG_KC_MASTER (R.string.msg_kc_master), + MSG_KC_REVOKE_BAD_ERR (R.string.msg_kc_revoke_bad_err), + MSG_KC_REVOKE_BAD_LOCAL (R.string.msg_kc_revoke_bad_local), + MSG_KC_REVOKE_BAD_TIME (R.string.msg_kc_revoke_bad_time), + MSG_KC_REVOKE_BAD_TYPE (R.string.msg_kc_revoke_bad_type), + MSG_KC_REVOKE_BAD (R.string.msg_kc_revoke_bad), + MSG_KC_REVOKE_DUP (R.string.msg_kc_revoke_dup), + MSG_KC_SUB (R.string.msg_kc_sub), + MSG_KC_SUB_BAD(R.string.msg_kc_sub_bad), + MSG_KC_SUB_BAD_ERR(R.string.msg_kc_sub_bad_err), + MSG_KC_SUB_BAD_LOCAL(R.string.msg_kc_sub_bad_local), + MSG_KC_SUB_BAD_KEYID(R.string.msg_kc_sub_bad_keyid), + MSG_KC_SUB_BAD_TIME(R.string.msg_kc_sub_bad_time), + MSG_KC_SUB_BAD_TYPE(R.string.msg_kc_sub_bad_type), + MSG_KC_SUB_PRIMARY_BAD(R.string.msg_kc_sub_primary_bad), + MSG_KC_SUB_PRIMARY_BAD_ERR(R.string.msg_kc_sub_primary_bad_err), + MSG_KC_SUB_PRIMARY_NONE(R.string.msg_kc_sub_primary_none), + MSG_KC_SUB_NO_CERT(R.string.msg_kc_sub_no_cert), + MSG_KC_SUB_REVOKE_BAD_ERR (R.string.msg_kc_sub_revoke_bad_err), + MSG_KC_SUB_REVOKE_BAD (R.string.msg_kc_sub_revoke_bad), + MSG_KC_SUB_REVOKE_DUP (R.string.msg_kc_sub_revoke_dup), + MSG_KC_SUCCESS_BAD (R.string.msg_kc_success_bad), + MSG_KC_SUCCESS_BAD_AND_RED (R.string.msg_kc_success_bad_and_red), + MSG_KC_SUCCESS_REDUNDANT (R.string.msg_kc_success_redundant), + MSG_KC_SUCCESS (R.string.msg_kc_success), + MSG_KC_UID_BAD_ERR (R.string.msg_kc_uid_bad_err), + MSG_KC_UID_BAD_LOCAL (R.string.msg_kc_uid_bad_local), + MSG_KC_UID_BAD_TIME (R.string.msg_kc_uid_bad_time), + MSG_KC_UID_BAD_TYPE (R.string.msg_kc_uid_bad_type), + MSG_KC_UID_BAD (R.string.msg_kc_uid_bad), + MSG_KC_UID_DUP (R.string.msg_kc_uid_dup), + MSG_KC_UID_FOREIGN (R.string.msg_kc_uid_foreign), + MSG_KC_UID_NO_CERT (R.string.msg_kc_uid_no_cert), + MSG_KC_UID_REVOKE_DUP (R.string.msg_kc_uid_revoke_dup), + MSG_KC_UID_REVOKE_OLD (R.string.msg_kc_uid_revoke_old), + + // keyring consolidation + MSG_MG_PUBLIC (R.string.msg_mg_public), + MSG_MG_SECRET (R.string.msg_mg_secret), + MSG_MG_FATAL_ENCODE (R.string.msg_mg_fatal_encode), + MSG_MG_HETEROGENEOUS (R.string.msg_mg_heterogeneous), + MSG_MG_NEW_SUBKEY (R.string.msg_mg_new_subkey), + MSG_MG_FOUND_NEW (R.string.msg_mg_found_new), + ; + + private final int mMsgId; + LogType(int msgId) { + mMsgId = msgId; + } + public int getMsgId() { + return mMsgId; + } + } + + /** Enumeration of possible log levels. */ + public static enum LogLevel { + DEBUG, + INFO, + WARN, + ERROR, // should occur once at the end of a failed operation + START, // should occur once at the start of each independent operation + OK, // should occur once at the end of a successful operation + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mResult); + dest.writeTypedList(mLog); + } + + public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() { + public OperationResultParcel createFromParcel(final Parcel source) { + return new OperationResultParcel(source); + } + + public OperationResultParcel[] newArray(final int size) { + return new OperationResultParcel[size]; + } + }; + + public static class OperationLog extends ArrayList<LogEntryParcel> { + + /// Simple convenience method + public void add(LogLevel level, LogType type, String[] parameters, int indent) { + Log.d(Constants.TAG, type.toString()); + add(new OperationResultParcel.LogEntryParcel(level, type, parameters, indent)); + } + + public boolean containsWarnings() { + for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(iterator())) { + if (entry.mLevel == LogLevel.WARN || entry.mLevel == LogLevel.ERROR) { + return true; + } + } + return false; + } + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java new file mode 100644 index 000000000..6c44b01f1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java @@ -0,0 +1,92 @@ +package org.sufficientlysecure.keychain.service; + +import android.os.Parcel; + +public abstract class OperationResults { + + public static class ImportResult extends OperationResultParcel { + + public final int mNewKeys, mUpdatedKeys, mBadKeys; + + // At least one new key + public static final int RESULT_OK_NEWKEYS = 2; + // At least one updated key + public static final int RESULT_OK_UPDATED = 4; + // At least one key failed (might still be an overall success) + public static final int RESULT_WITH_ERRORS = 8; + // There are warnings in the log + public static final int RESULT_WITH_WARNINGS = 16; + + // No keys to import... + public static final int RESULT_FAIL_NOTHING = 32 +1; + + public boolean isOkBoth() { + return (mResult & (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED)) + == (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED); + } + public boolean isOkNew() { + return (mResult & RESULT_OK_NEWKEYS) == RESULT_OK_NEWKEYS; + } + public boolean isOkUpdated() { + return (mResult & RESULT_OK_UPDATED) == RESULT_OK_UPDATED; + } + public boolean isFailNothing() { + return (mResult & RESULT_FAIL_NOTHING) == RESULT_FAIL_NOTHING; + } + + public ImportResult(Parcel source) { + super(source); + mNewKeys = source.readInt(); + mUpdatedKeys = source.readInt(); + mBadKeys = source.readInt(); + } + + public ImportResult(int result, OperationLog log, + int newKeys, int updatedKeys, int badKeys) { + super(result, log); + mNewKeys = newKeys; + mUpdatedKeys = updatedKeys; + mBadKeys = badKeys; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mNewKeys); + dest.writeInt(mUpdatedKeys); + dest.writeInt(mBadKeys); + } + + public static Creator<ImportResult> CREATOR = new Creator<ImportResult>() { + public ImportResult createFromParcel(final Parcel source) { + return new ImportResult(source); + } + + public ImportResult[] newArray(final int size) { + return new ImportResult[size]; + } + }; + + } + + public static class SaveKeyringResult extends OperationResultParcel { + + public SaveKeyringResult(int result, OperationLog log) { + super(result, log); + } + + // Some old key was updated + public static final int UPDATED = 2; + + // Public key was saved + public static final int SAVED_PUBLIC = 8; + // Secret key was saved (not exclusive with public!) + public static final int SAVED_SECRET = 16; + + public boolean updated() { + return (mResult & UPDATED) == UPDATED; + } + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java index d953e2591..7fdab7bdd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.util.Notify; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; import org.sufficientlysecure.keychain.util.Log; @@ -115,7 +116,8 @@ public class DecryptFileFragment extends DecryptFragment { } if (mInputFilename.equals("")) { - AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); + //AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); + Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR); return; } 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 48602aaa1..54186e380 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -32,28 +32,39 @@ import android.os.Messenger; import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; +import android.util.DisplayMetrics; +import android.util.TypedValue; import android.view.View; import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.widget.ArrayAdapter; -import com.devspark.appmsg.AppMsg; +import com.github.johnpersano.supertoasts.SuperCardToast; +import com.github.johnpersano.supertoasts.SuperToast; +import com.github.johnpersano.supertoasts.util.OnClickWrapper; +import com.github.johnpersano.supertoasts.util.Style; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.ui.dialog.BadImportKeyDialogFragment; +import org.sufficientlysecure.keychain.service.OperationResults.ImportResult; +import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; +import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; import java.util.Locale; -public class ImportKeysActivity extends ActionBarActivity implements ActionBar.OnNavigationListener { +public class ImportKeysActivity extends ActionBarActivity { public static final String ACTION_IMPORT_KEY = Constants.INTENT_PREFIX + "IMPORT_KEY"; public static final String ACTION_IMPORT_KEY_FROM_QR_CODE = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_QR_CODE"; @@ -87,23 +98,18 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O private String[] mNavigationStrings; private Fragment mCurrentFragment; private View mImportButton; + private ViewPager mViewPager; + private SlidingTabLayout mSlidingTabLayout; + private PagerTabStripAdapter mTabsAdapter; + + public static final int VIEW_PAGER_HEIGHT = 64; // dp - private static final Class[] NAVIGATION_CLASSES = new Class[]{ - ImportKeysServerFragment.class, - ImportKeysFileFragment.class, - ImportKeysQrCodeFragment.class, - ImportKeysClipboardFragment.class, - ImportKeysNFCFragment.class, - ImportKeysKeybaseFragment.class - }; private static final int NAV_SERVER = 0; - private static final int NAV_FILE = 1; - private static final int NAV_QR_CODE = 2; - private static final int NAV_CLIPBOARD = 3; - private static final int NAV_NFC = 4; - private static final int NAV_KEYBASE = 5; + private static final int NAV_QR_CODE = 1; + private static final int NAV_FILE = 2; + private static final int NAV_KEYBASE = 3; - private int mCurrentNavPosition = -1; + private int mSwitchToTab = NAV_SERVER; @Override protected void onCreate(Bundle savedInstanceState) { @@ -111,6 +117,9 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O setContentView(R.layout.import_keys_activity); + mViewPager = (ViewPager) findViewById(R.id.import_pager); + mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.import_sliding_tab_layout); + mImportButton = findViewById(R.id.import_import); mImportButton.setOnClickListener(new OnClickListener() { @Override @@ -124,19 +133,57 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) { setTitle(R.string.nav_import); } else { - getSupportActionBar().setDisplayShowTitleEnabled(false); - - // set drop down navigation - Context context = getSupportActionBar().getThemedContext(); - ArrayAdapter<CharSequence> navigationAdapter = ArrayAdapter.createFromResource(context, - R.array.import_action_list, android.R.layout.simple_spinner_dropdown_item); - getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); - getSupportActionBar().setListNavigationCallbacks(navigationAdapter, this); + initTabs(); } handleActions(savedInstanceState, getIntent()); } + private void initTabs() { + mTabsAdapter = new PagerTabStripAdapter(this); + mViewPager.setAdapter(mTabsAdapter); + 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 + public void onPageSelected(int position) { + } + + @Override + public void onPageScrollStateChanged(int state) { + } + }); + + Bundle serverBundle = new Bundle(); +// serverBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ImportKeysServerFragment.class, + serverBundle, getString(R.string.import_tab_keyserver)); + + Bundle qrCodeBundle = new Bundle(); +// importBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ImportKeysQrCodeFragment.class, + qrCodeBundle, getString(R.string.import_tab_qr_code)); + + Bundle fileBundle = new Bundle(); +// importBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ImportKeysFileFragment.class, + fileBundle, getString(R.string.import_tab_direct)); + + Bundle keybaseBundle = new Bundle(); +// keybaseBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ImportKeysKeybaseFragment.class, + keybaseBundle, getString(R.string.import_tab_keybase)); + + // update layout after operations + mSlidingTabLayout.setViewPager(mViewPager); + } + protected void handleActions(Bundle savedInstanceState, Intent intent) { String action = intent.getAction(); Bundle extras = intent.getExtras(); @@ -160,7 +207,7 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O /* Keychain's own Actions */ // display file fragment - loadNavFragment(NAV_FILE, null); + mViewPager.setCurrentItem(NAV_FILE); if (dataUri != null) { // action: directly load data @@ -195,7 +242,9 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O // display keyserver fragment with query Bundle args = new Bundle(); args.putString(ImportKeysServerFragment.ARG_QUERY, query); - loadNavFragment(NAV_SERVER, args); +// loadNavFragment(NAV_SERVER, args); + //TODO: load afterwards! + mSwitchToTab = NAV_SERVER; // action: search immediately startListFragment(savedInstanceState, null, null, query); @@ -219,9 +268,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O return; } } else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) { - // NOTE: this only displays the appropriate fragment, no actions are taken - loadNavFragment(NAV_FILE, null); + mSwitchToTab = NAV_FILE; // no immediate actions! startListFragment(savedInstanceState, null, null, null); @@ -229,26 +277,28 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O // also exposed in AndroidManifest // NOTE: this only displays the appropriate fragment, no actions are taken - loadNavFragment(NAV_QR_CODE, null); + mSwitchToTab = NAV_QR_CODE; // no immediate actions! startListFragment(savedInstanceState, null, null, null); } else if (ACTION_IMPORT_KEY_FROM_NFC.equals(action)) { // NOTE: this only displays the appropriate fragment, no actions are taken - loadNavFragment(NAV_NFC, null); + mSwitchToTab = NAV_QR_CODE; // no immediate actions! startListFragment(savedInstanceState, null, null, null); } else if (ACTION_IMPORT_KEY_FROM_KEYBASE.equals(action)) { // NOTE: this only displays the appropriate fragment, no actions are taken - loadNavFragment(NAV_KEYBASE, null); + mSwitchToTab = NAV_KEYBASE; // no immediate actions! startListFragment(savedInstanceState, null, null, null); } else { startListFragment(savedInstanceState, null, null, null); } + + mViewPager.setCurrentItem(mSwitchToTab); } private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri, String serverQuery) { @@ -271,54 +321,16 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O getSupportFragmentManager().executePendingTransactions(); } - /** - * "Basically, when using a list navigation, onNavigationItemSelected() is automatically - * called when your activity is created/re-created, whether you like it or not. To prevent - * your Fragment's onCreateView() from being called twice, this initial automatic call to - * onNavigationItemSelected() should check whether the Fragment is already in existence - * inside your Activity." - * <p/> - * from http://stackoverflow.com/a/14295474 - * <p/> - * In our case, if we start ImportKeysActivity with parameters to directly search using a fingerprint, - * the fragment would be loaded twice resulting in the query being empty after the second load. - * <p/> - * Our solution: - * To prevent that a fragment will be loaded again even if it was already loaded loadNavFragment - * checks against mCurrentNavPosition. - * - * @param itemPosition - * @param itemId - * @return - */ - @Override - public boolean onNavigationItemSelected(int itemPosition, long itemId) { - Log.d(Constants.TAG, "onNavigationItemSelected"); - - loadNavFragment(itemPosition, null); - - return true; + public void resizeViewPager(int dp) { + ViewGroup.LayoutParams params = mViewPager.getLayoutParams(); + params.height = OtherHelper.dpToPx(this, dp); + // update layout after operations + mSlidingTabLayout.setViewPager(mViewPager); } - private void loadNavFragment(int itemPosition, Bundle args) { - if (mCurrentNavPosition != itemPosition) { - if (ActionBar.NAVIGATION_MODE_LIST == getSupportActionBar().getNavigationMode()) { - getSupportActionBar().setSelectedNavigationItem(itemPosition); - } - loadFragment(NAVIGATION_CLASSES[itemPosition], args, mNavigationStrings[itemPosition]); - mCurrentNavPosition = itemPosition; - } - } - - private void loadFragment(Class<?> clss, Bundle args, String tag) { - mCurrentFragment = Fragment.instantiate(this, clss.getName(), args); - - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - // Replace whatever is in the fragment container with this fragment - // and give the fragment a tag name equal to the string at the position selected - ft.replace(R.id.import_navigation_fragment, mCurrentFragment, tag); - // Apply changes - ft.commit(); + public int getViewPagerHeight() { + ViewGroup.LayoutParams params = mViewPager.getLayoutParams(); + return OtherHelper.pxToDp(this, params.height); } public void loadFromFingerprintUri(Bundle savedInstanceState, Uri dataUri) { @@ -331,8 +343,11 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O public void loadFromFingerprint(Bundle savedInstanceState, String fingerprint) { if (fingerprint == null || fingerprint.length() < 40) { - AppMsg.makeText(this, R.string.import_qr_code_too_short_fingerprint, - AppMsg.STYLE_ALERT).show(); + SuperCardToast toast = SuperCardToast.create(this, + getString(R.string.import_qr_code_too_short_fingerprint), + SuperToast.Duration.LONG); + toast.setBackground(SuperToast.Background.RED); + toast.show(); return; } @@ -342,7 +357,9 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O Bundle args = new Bundle(); args.putString(ImportKeysServerFragment.ARG_QUERY, query); args.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true); - loadNavFragment(NAV_SERVER, args); +// loadNavFragment(NAV_SERVER, args); + + //TODO // action: search directly startListFragment(savedInstanceState, null, null, query); @@ -368,39 +385,94 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { // get returned data bundle Bundle returnData = message.getData(); + final ImportResult result = + returnData.<ImportResult>getParcelable(KeychainIntentService.RESULT); + + int resultType = result.getResult(); + + String str; + int duration, color; + + // Not an overall failure + if ((resultType & ImportResult.RESULT_ERROR) == 0) { + String withWarnings; + + // Any warnings? + if ((resultType & ImportResult.RESULT_WITH_WARNINGS) > 0) { + duration = 0; + color = Style.ORANGE; + withWarnings = getResources().getString(R.string.import_with_warnings); + } else { + duration = SuperToast.Duration.LONG; + color = Style.GREEN; + withWarnings = ""; + } + + // New and updated keys + if (result.isOkBoth()) { + str = getResources().getQuantityString( + R.plurals.import_keys_added_and_updated_1, result.mNewKeys, result.mNewKeys); + str += getResources().getQuantityString( + R.plurals.import_keys_added_and_updated_2, result.mUpdatedKeys, result.mUpdatedKeys, withWarnings); + } else if (result.isOkUpdated()) { + str = getResources().getQuantityString( + R.plurals.import_keys_updated, result.mUpdatedKeys, result.mUpdatedKeys, withWarnings); + } else if (result.isOkNew()) { + str = getResources().getQuantityString( + R.plurals.import_keys_added, result.mNewKeys, result.mNewKeys, withWarnings); + } else { + duration = 0; + color = Style.RED; + str = "internal error"; + } - int added = returnData.getInt(KeychainIntentService.RESULT_IMPORT_ADDED); - int updated = returnData - .getInt(KeychainIntentService.RESULT_IMPORT_UPDATED); - int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD); - String toastMessage; - if (added > 0 && updated > 0) { - String addedStr = getResources().getQuantityString( - R.plurals.keys_added_and_updated_1, added, added); - String updatedStr = getResources().getQuantityString( - R.plurals.keys_added_and_updated_2, updated, updated); - toastMessage = addedStr + updatedStr; - } else if (added > 0) { - toastMessage = getResources().getQuantityString(R.plurals.keys_added, - added, added); - } else if (updated > 0) { - toastMessage = getResources().getQuantityString(R.plurals.keys_updated, - updated, updated); } else { - toastMessage = getString(R.string.no_keys_added_or_updated); + duration = 0; + color = Style.RED; + if (result.isFailNothing()) { + str = getString(R.string.import_error_nothing); + } else { + str = getString(R.string.import_error); + } } - AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO) - .show(); + + SuperCardToast toast = new SuperCardToast(ImportKeysActivity.this, + SuperToast.Type.BUTTON, Style.getStyle(color, SuperToast.Animations.POPUP)); + toast.setText(str); + toast.setDuration(duration); + toast.setIndeterminate(duration == 0); + toast.setSwipeToDismiss(true); + toast.setButtonIcon(R.drawable.ic_action_view_as_list, + getResources().getString(R.string.import_view_log)); + toast.setButtonTextColor(getResources().getColor(R.color.black)); + toast.setTextColor(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( + ImportKeysActivity.this, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result); + startActivity(intent); + } + } + )); + toast.show(); + + /* if (bad > 0) { BadImportKeyDialogFragment badImportKeyDialogFragment = BadImportKeyDialogFragment.newInstance(bad); badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog"); } + */ + /* if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) { ImportKeysActivity.this.setResult(Activity.RESULT_OK, mPendingIntentData); finish(); } + */ } } }; @@ -483,7 +555,11 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O startService(intent); } else { - AppMsg.makeText(this, R.string.error_nothing_import, AppMsg.STYLE_ALERT).show(); + SuperCardToast toast = SuperCardToast.create(this, + getString(R.string.error_nothing_import), + SuperToast.Duration.LONG); + toast.setBackground(SuperToast.Background.RED); + toast.show(); } } @@ -495,9 +571,12 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O super.onResume(); // Check to see if the Activity started due to an Android Beam - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN - && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { - handleActionNdefDiscovered(getIntent()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { + handleActionNdefDiscovered(getIntent()); + } else { + Log.d(Constants.TAG, "NFC: No NDEF discovered!"); + } } else { Log.e(Constants.TAG, "Android Beam not supported by Android < 4.1"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java deleted file mode 100644 index f331358fa..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2013-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.net.Uri; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; - -import com.beardedhen.androidbootstrap.BootstrapButton; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; - -import java.util.Locale; - -public class ImportKeysClipboardFragment extends Fragment { - - private ImportKeysActivity mImportActivity; - private BootstrapButton mButton; - - /** - * Creates new instance of this fragment - */ - public static ImportKeysClipboardFragment newInstance() { - ImportKeysClipboardFragment frag = new ImportKeysClipboardFragment(); - - Bundle args = new Bundle(); - frag.setArguments(args); - - return frag; - } - - /** - * Inflate the layout for this fragment - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.import_keys_clipboard_fragment, container, false); - - mButton = (BootstrapButton) view.findViewById(R.id.import_clipboard_button); - mButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity()); - String sendText = ""; - if (clipboardText != null) { - sendText = clipboardText.toString(); - if (sendText.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) { - mImportActivity.loadFromFingerprintUri(null, Uri.parse(sendText)); - return; - } - } - mImportActivity.loadCallback(sendText.getBytes(), null, null, null, null); - } - }); - - return view; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - mImportActivity = (ImportKeysActivity) getActivity(); - } - -} 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 51f961aab..060e9bab2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -19,21 +19,24 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.beardedhen.androidbootstrap.BootstrapButton; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.helper.FileHelper; +import java.util.Locale; + public class ImportKeysFileFragment extends Fragment { private ImportKeysActivity mImportActivity; - private BootstrapButton mBrowse; + private View mBrowse; + private View mClipboardButton; public static final int REQUEST_CODE_FILE = 0x00007003; @@ -56,26 +59,45 @@ public class ImportKeysFileFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.import_keys_file_fragment, container, false); - mBrowse = (BootstrapButton) view.findViewById(R.id.import_keys_file_browse); + mBrowse = view.findViewById(R.id.import_keys_file_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // open .asc or .gpg files - // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc + // setting it to text/plain prevents Cyanogenmod's file manager from selecting asc // or gpg types! FileHelper.openFile(ImportKeysFileFragment.this, Constants.Path.APP_DIR + "/", "*/*", REQUEST_CODE_FILE); } }); + mClipboardButton = view.findViewById(R.id.import_clipboard_button); + mClipboardButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity()); + String sendText = ""; + if (clipboardText != null) { + sendText = clipboardText.toString(); + if (sendText.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) { + mImportActivity.loadFromFingerprintUri(null, Uri.parse(sendText)); + return; + } + } + mImportActivity.loadCallback(sendText.getBytes(), null, null, null, null); + } + }); + + return view; } @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + public void onAttach(Activity activity) { + super.onAttach(activity); - mImportActivity = (ImportKeysActivity) getActivity(); + mImportActivity = (ImportKeysActivity) activity; } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java index a639fe0e0..9264829ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; @@ -29,8 +30,6 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.TextView; -import com.beardedhen.androidbootstrap.BootstrapButton; - import org.sufficientlysecure.keychain.R; /** @@ -40,7 +39,7 @@ import org.sufficientlysecure.keychain.R; public class ImportKeysKeybaseFragment extends Fragment { private ImportKeysActivity mImportActivity; - private BootstrapButton mSearchButton; + private View mSearchButton; private EditText mQueryEditText; public static final String ARG_QUERY = "query"; @@ -66,7 +65,7 @@ public class ImportKeysKeybaseFragment extends Fragment { mQueryEditText = (EditText) view.findViewById(R.id.import_keybase_query); - mSearchButton = (BootstrapButton) view.findViewById(R.id.import_keybase_search); + mSearchButton = view.findViewById(R.id.import_keybase_search); mSearchButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -101,8 +100,6 @@ public class ImportKeysKeybaseFragment extends Fragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mImportActivity = (ImportKeysActivity) getActivity(); - // set displayed values if (getArguments() != null) { if (getArguments().containsKey(ARG_QUERY)) { @@ -112,6 +109,13 @@ public class ImportKeysKeybaseFragment extends Fragment { } } + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + mImportActivity = (ImportKeysActivity) activity; + } + private void search(String query) { mImportActivity.loadCallback(null, null, null, null, query); } 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 d91c55da3..7d8dc4a6c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -42,6 +42,7 @@ import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Notify; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; @@ -97,7 +98,7 @@ public class ImportKeysListFragment extends ListFragment implements public ArrayList<ParcelableKeyRing> getSelectedData() { ArrayList<ParcelableKeyRing> result = new ArrayList<ParcelableKeyRing>(); - for(ImportKeysListEntry entry : getSelectedEntries()) { + for (ImportKeysListEntry entry : getSelectedEntries()) { result.add(mCachedKeyData.get(entry.getKeyId())); } return result; @@ -273,17 +274,15 @@ public class ImportKeysListFragment extends ListFragment implements // No error mCachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings(); } else if (error instanceof ImportKeysListLoader.FileHasNoContent) { - AppMsg.makeText(getActivity(), R.string.error_import_file_no_content, - AppMsg.STYLE_ALERT).show(); + Notify.showNotify(getActivity(), R.string.error_import_file_no_content, Notify.Style.ERROR); } else if (error instanceof ImportKeysListLoader.NonPgpPart) { - AppMsg.makeText(getActivity(), + Notify.showNotify(getActivity(), ((ImportKeysListLoader.NonPgpPart) error).getCount() + " " + getResources(). getQuantityString(R.plurals.error_import_non_pgp_part, ((ImportKeysListLoader.NonPgpPart) error).getCount()), - new AppMsg.Style(AppMsg.LENGTH_LONG, R.color.confirm)).show(); + Notify.Style.OK); } else { - AppMsg.makeText(getActivity(), R.string.error_generic_report_bug, - new AppMsg.Style(AppMsg.LENGTH_LONG, R.color.alert)).show(); + Notify.showNotify(getActivity(), R.string.error_generic_report_bug, Notify.Style.ERROR); } break; @@ -292,23 +291,17 @@ public class ImportKeysListFragment extends ListFragment implements // TODO: possibly fine-tune message building for these two cases if (error == null) { - AppMsg.makeText( - getActivity(), getResources().getQuantityString(R.plurals.keys_found, - mAdapter.getCount(), mAdapter.getCount()), - AppMsg.STYLE_INFO - ).show(); + // No error } else if (error instanceof Keyserver.QueryTooShortException) { - AppMsg.makeText(getActivity(), R.string.error_keyserver_insufficient_query, - AppMsg.STYLE_ALERT).show(); + Notify.showNotify(getActivity(), R.string.error_keyserver_insufficient_query, Notify.Style.ERROR); } else if (error instanceof Keyserver.TooManyResponsesException) { - AppMsg.makeText(getActivity(), R.string.error_keyserver_too_many_responses, - AppMsg.STYLE_ALERT).show(); + Notify.showNotify(getActivity(), R.string.error_keyserver_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() + ")"; - AppMsg.makeText(getActivity(), alert, AppMsg.STYLE_ALERT).show(); + Notify.showNotify(getActivity(), alert, Notify.Style.ERROR); } break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java deleted file mode 100644 index 45f464b1c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2013-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.content.Intent; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; - -import com.beardedhen.androidbootstrap.BootstrapButton; - -import org.sufficientlysecure.keychain.R; - -public class ImportKeysNFCFragment extends Fragment { - - private BootstrapButton mButton; - - /** - * Creates new instance of this fragment - */ - public static ImportKeysNFCFragment newInstance() { - ImportKeysNFCFragment frag = new ImportKeysNFCFragment(); - - Bundle args = new Bundle(); - frag.setArguments(args); - - return frag; - } - - /** - * Inflate the layout for this fragment - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.import_keys_nfc_fragment, container, false); - - mButton = (BootstrapButton) view.findViewById(R.id.import_nfc_button); - mButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - // show nfc help - Intent intent = new Intent(getActivity(), HelpActivity.class); - intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, HelpActivity.TAB_NFC); - startActivityForResult(intent, 0); - } - }); - - return view; - } - -} 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 22b56e1ab..0cbb51c77 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java @@ -17,47 +17,47 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; -import com.beardedhen.androidbootstrap.BootstrapButton; -import com.devspark.appmsg.AppMsg; import com.google.zxing.integration.android.IntentResult; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Notify; import java.util.ArrayList; import java.util.Locale; public class ImportKeysQrCodeFragment extends Fragment { - private ImportKeysActivity mImportActivity; - private BootstrapButton mButton; - private TextView mText; - private ProgressBar mProgress; + private View mNfcButton; + + private View mQrCodeButton; + private TextView mQrCodeText; + private ProgressBar mQrCodeProgress; - private String[] mScannedContent; + private String[] mQrCodeContent; /** * Creates new instance of this fragment */ - public static ImportKeysQrCodeFragment newInstance() { - ImportKeysQrCodeFragment frag = new ImportKeysQrCodeFragment(); + public static ImportKeysFileFragment newInstance() { + ImportKeysFileFragment frag = new ImportKeysFileFragment(); Bundle args = new Bundle(); - frag.setArguments(args); + frag.setArguments(args); return frag; } @@ -68,11 +68,23 @@ public class ImportKeysQrCodeFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.import_keys_qr_code_fragment, container, false); - mButton = (BootstrapButton) view.findViewById(R.id.import_qrcode_button); - mText = (TextView) view.findViewById(R.id.import_qrcode_text); - mProgress = (ProgressBar) view.findViewById(R.id.import_qrcode_progress); + mNfcButton = view.findViewById(R.id.import_nfc_button); + mNfcButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + // show nfc help + Intent intent = new Intent(getActivity(), HelpActivity.class); + intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, HelpActivity.TAB_NFC); + startActivityForResult(intent, 0); + } + }); + + mQrCodeButton = view.findViewById(R.id.import_qrcode_button); + mQrCodeText = (TextView) view.findViewById(R.id.import_qrcode_text); + mQrCodeProgress = (ProgressBar) view.findViewById(R.id.import_qrcode_progress); - mButton.setOnClickListener(new OnClickListener() { + mQrCodeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -85,10 +97,10 @@ public class ImportKeysQrCodeFragment extends Fragment { } @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + public void onAttach(Activity activity) { + super.onAttach(activity); - mImportActivity = (ImportKeysActivity) getActivity(); + mImportActivity = (ImportKeysActivity) activity; } @Override @@ -122,8 +134,7 @@ public class ImportKeysQrCodeFragment extends Fragment { } // fail... - AppMsg.makeText(getActivity(), R.string.import_qr_code_wrong, AppMsg.STYLE_ALERT) - .show(); + Notify.showNotify(getActivity(), R.string.import_qr_code_wrong, Notify.Style.ERROR); } break; @@ -136,6 +147,7 @@ public class ImportKeysQrCodeFragment extends Fragment { } } + public void importFingerprint(Uri dataUri) { mImportActivity.loadFromFingerprintUri(null, dataUri); } @@ -151,32 +163,31 @@ public class ImportKeysQrCodeFragment extends Fragment { // first qr code -> setup if (counter == 0) { - mScannedContent = new String[size]; - mProgress.setMax(size); - mProgress.setVisibility(View.VISIBLE); - mText.setVisibility(View.VISIBLE); + mQrCodeContent = new String[size]; + mQrCodeProgress.setMax(size); + mQrCodeProgress.setVisibility(View.VISIBLE); + mQrCodeText.setVisibility(View.VISIBLE); } - if (mScannedContent == null || counter > mScannedContent.length) { - AppMsg.makeText(getActivity(), R.string.import_qr_code_start_with_one, AppMsg.STYLE_ALERT) - .show(); + if (mQrCodeContent == null || counter > mQrCodeContent.length) { + Notify.showNotify(getActivity(), R.string.import_qr_code_start_with_one, Notify.Style.ERROR); return; } // save scanned content - mScannedContent[counter] = content; + mQrCodeContent[counter] = content; // get missing numbers ArrayList<Integer> missing = new ArrayList<Integer>(); - for (int i = 0; i < mScannedContent.length; i++) { - if (mScannedContent[i] == null) { + for (int i = 0; i < mQrCodeContent.length; i++) { + if (mQrCodeContent[i] == null) { missing.add(i); } } // update progress and text - int alreadyScanned = mScannedContent.length - missing.size(); - mProgress.setProgress(alreadyScanned); + int alreadyScanned = mQrCodeContent.length - missing.size(); + mQrCodeProgress.setProgress(alreadyScanned); String missingString = ""; for (int m : missing) { @@ -188,17 +199,16 @@ public class ImportKeysQrCodeFragment extends Fragment { String missingText = getResources().getQuantityString(R.plurals.import_qr_code_missing, missing.size(), missingString); - mText.setText(missingText); + mQrCodeText.setText(missingText); // finished! if (missing.size() == 0) { - mText.setText(R.string.import_qr_code_finished); + mQrCodeText.setText(R.string.import_qr_code_finished); String result = ""; - for (String in : mScannedContent) { + for (String in : mQrCodeContent) { result += in; } mImportActivity.loadCallback(result.getBytes(), null, null, null, null); } } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java index 9e3d88ff5..eabc8348c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; @@ -32,8 +33,6 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; -import com.beardedhen.androidbootstrap.BootstrapButton; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.Preferences; @@ -46,8 +45,10 @@ public class ImportKeysServerFragment extends Fragment { private ImportKeysActivity mImportActivity; - private BootstrapButton mSearchButton; + private View mSearchButton; private EditText mQueryEditText; + private View mConfigButton; + private View mConfigLayout; private Spinner mServerSpinner; private ArrayAdapter<String> mServerAdapter; @@ -73,14 +74,17 @@ public class ImportKeysServerFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.import_keys_server_fragment, container, false); - mSearchButton = (BootstrapButton) view.findViewById(R.id.import_server_search); + mSearchButton = view.findViewById(R.id.import_server_search); mQueryEditText = (EditText) view.findViewById(R.id.import_server_query); + mConfigButton = view.findViewById(R.id.import_server_config_button); + mConfigLayout = view.findViewById(R.id.import_server_config); mServerSpinner = (Spinner) view.findViewById(R.id.import_server_spinner); // add keyservers to spinner mServerAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_spinner_item, Preferences.getPreferences(getActivity()) - .getKeyServers()); + .getKeyServers() + ); mServerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mServerSpinner.setAdapter(mServerAdapter); if (mServerAdapter.getCount() > 0) { @@ -118,6 +122,17 @@ public class ImportKeysServerFragment extends Fragment { } }); + mConfigButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mImportActivity.getViewPagerHeight() > ImportKeysActivity.VIEW_PAGER_HEIGHT) { + mImportActivity.resizeViewPager(ImportKeysActivity.VIEW_PAGER_HEIGHT); + } else { + mImportActivity.resizeViewPager(ImportKeysActivity.VIEW_PAGER_HEIGHT + 41); + } + } + }); + return view; } @@ -125,8 +140,6 @@ public class ImportKeysServerFragment extends Fragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mImportActivity = (ImportKeysActivity) getActivity(); - // set displayed values if (getArguments() != null) { if (getArguments().containsKey(ARG_QUERY)) { @@ -150,6 +163,13 @@ public class ImportKeysServerFragment extends Fragment { } } + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + mImportActivity = (ImportKeysActivity) activity; + } + private void search(String query, String keyServer) { mImportActivity.loadCallback(null, null, query, keyServer, null); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java new file mode 100644 index 000000000..a0d449195 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java @@ -0,0 +1,21 @@ +package org.sufficientlysecure.keychain.ui; + +import android.os.Bundle; +import android.support.v4.view.GestureDetectorCompat; +import android.support.v7.app.ActionBarActivity; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.MotionEvent; + +import org.sufficientlysecure.keychain.R; + +public class LogDisplayActivity extends ActionBarActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.log_display_activity); + } + +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java new file mode 100644 index 000000000..496e98c18 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -0,0 +1,175 @@ +package org.sufficientlysecure.keychain.ui; + +import android.content.Context; +import android.content.Intent; +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.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.OperationResultParcel; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogEntryParcel; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; + +public class LogDisplayFragment extends ListFragment implements OnTouchListener { + + HashMap<LogLevel,LogAdapter> mAdapters = new HashMap<LogLevel, LogAdapter>(); + LogAdapter mAdapter; + LogLevel mLevel = LogLevel.DEBUG; + + OperationResultParcel mResult; + + GestureDetector mDetector; + + public static final String EXTRA_RESULT = "log"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Intent intent = getActivity().getIntent(); + if (intent.getExtras() == null || !intent.getExtras().containsKey(EXTRA_RESULT)) { + getActivity().finish(); + return; + } + + mResult = intent.<OperationResultParcel>getParcelableExtra(EXTRA_RESULT); + if (mResult == null) { + getActivity().finish(); + return; + } + + mAdapter = new LogAdapter(getActivity(), mResult.getLog(), LogLevel.DEBUG); + mAdapters.put(LogLevel.DEBUG, mAdapter); + 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; + } + }); + + } + + 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 void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getListView().setDividerHeight(0); + getListView().setOnTouchListener(this); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + mDetector.onTouchEvent(event); + return false; + } + + private class LogAdapter extends ArrayAdapter<LogEntryParcel> { + + private LayoutInflater mInflater; + private int dipFactor; + + public LogAdapter(Context context, ArrayList<LogEntryParcel> log, LogLevel level) { + super(context, R.layout.log_display_item); + 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.mLevel.ordinal() >= level.ordinal()) { + add(e); + } + } + notifyDataSetChanged(); + } + + private class ItemHolder { + final TextView mText; + final ImageView mImg; + public ItemHolder(TextView text, ImageView image) { + mText = text; + mImg = image; + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + LogEntryParcel entry = getItem(position); + ItemHolder ih; + if (convertView == null) { + 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) + ); + convertView.setTag(ih); + } else { + ih = (ItemHolder) convertView.getTag(); + } + + ih.mText.setText(getResources().getString(entry.mType.getMsgId(), (Object[]) entry.mParameters)); + ih.mText.setTextColor(entry.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK); + convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0); + switch (entry.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 OK: ih.mImg.setBackgroundColor(Color.GREEN); break; + } + + return convertView; + } + + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 463c800d2..1912b6e7d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -56,6 +56,7 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; +import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout.TabColorizer; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; @@ -121,6 +122,18 @@ public class ViewKeyActivity extends ActionBarActivity implements mViewPager = (ViewPager) findViewById(R.id.view_key_pager); mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.view_key_sliding_tab_layout); + mSlidingTabLayout.setCustomTabColorizer(new TabColorizer() { + @Override + public int getIndicatorColor(int position) { + return position == TAB_CERTS || position == TAB_KEYS ? 0xFFFF4444 : 0xFFAA66CC; + } + + @Override + public int getDividerColor(int position) { + return 0; + } + }); + int switchToTab = TAB_MAIN; Intent intent = getIntent(); if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) { @@ -158,7 +171,7 @@ public class ViewKeyActivity extends ActionBarActivity implements Bundle shareBundle = new Bundle(); shareBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri); mTabsAdapter.addTab(ViewKeyShareFragment.class, - mainBundle, getString(R.string.key_view_tab_share)); + shareBundle, getString(R.string.key_view_tab_share)); // update layout after operations mSlidingTabLayout.setViewPager(mViewPager); 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 03a82696d..c2712e89e 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 @@ -133,7 +133,7 @@ public class ImportKeysListLoader // read all available blocks... (asc files can contain many blocks with BEGIN END) while (bufferedInput.available() > 0) { - // todo deal with non-keyring objects? + // TODO: deal with non-keyring objects? List<UncachedKeyRing> rings = UncachedKeyRing.fromStream(bufferedInput); for(UncachedKeyRing key : rings) { ImportKeysListEntry item = new ImportKeysListEntry(getContext(), key); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Notify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Notify.java new file mode 100644 index 000000000..67f81fb24 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Notify.java @@ -0,0 +1,70 @@ +/* + * 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.util; + +import android.app.Activity; +import android.content.res.Resources; + +import com.github.johnpersano.supertoasts.SuperCardToast; +import com.github.johnpersano.supertoasts.SuperToast; + +/** + * @author danielhass + * Notify wrapper which allows a more easy use of different notification libraries + */ +public class Notify { + + public static enum Style {OK, WARN, ERROR} + + /** + * Shows a simple in-layout notification with the CharSequence given as parameter + * @param activity + * @param text Text to show + * @param style Notification styling + */ + public static void showNotify(Activity activity, CharSequence text, Style style) { + + SuperCardToast st = new SuperCardToast(activity); + st.setText(text); + st.setDuration(SuperToast.Duration.MEDIUM); + switch (style){ + case OK: + st.setBackground(SuperToast.Background.GREEN); + break; + case WARN: + st.setBackground(SuperToast.Background.ORANGE); + break; + case ERROR: + st.setBackground(SuperToast.Background.RED); + break; + } + st.show(); + + } + + /** + * Shows a simple in-layout notification with the resource text from given id + * @param activity + * @param resId ResourceId of notification text + * @param style Notification styling + * @throws Resources.NotFoundException + */ + public static void showNotify(Activity activity, int resId, Style style) throws Resources.NotFoundException { + showNotify(activity, activity.getResources().getText(resId), style); + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_collection.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_collection.png Binary files differnew file mode 100644 index 000000000..8de91173c --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_collection.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_qr_code.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_qr_code.png Binary files differnew file mode 100644 index 000000000..e15a055db --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_qr_code.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_view_as_list.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_view_as_list.png Binary files differnew file mode 100644 index 000000000..86da228e9 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_view_as_list.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_collection.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_collection.png Binary files differnew file mode 100644 index 000000000..b89ea93ff --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_collection.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_qr_code.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_qr_code.png Binary files differnew file mode 100644 index 000000000..1c65e5af8 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_qr_code.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_view_as_list.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_view_as_list.png Binary files differnew file mode 100644 index 000000000..ccb4c7d7b --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_view_as_list.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_collection.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_collection.png Binary files differnew file mode 100644 index 000000000..88240fd30 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_collection.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_qr_code.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_qr_code.png Binary files differnew file mode 100644 index 000000000..c56b128e4 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_qr_code.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_view_as_list.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_view_as_list.png Binary files differnew file mode 100644 index 000000000..b9c93c8c2 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_view_as_list.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_collection.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_collection.png Binary files differnew file mode 100644 index 000000000..c41ca8c8b --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_collection.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_qr_code.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_qr_code.png Binary files differnew file mode 100644 index 000000000..c718aee0b --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_qr_code.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_view_as_list.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_view_as_list.png Binary files differnew file mode 100644 index 000000000..460041640 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_view_as_list.png diff --git a/OpenKeychain/src/main/res/layout/decrypt_content.xml b/OpenKeychain/src/main/res/layout/decrypt_content.xml index a496d8b9d..866857143 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_content.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_content.xml @@ -6,6 +6,8 @@ android:layout_height="match_parent" android:orientation="vertical"> + <include layout="@layout/notification_area"/> + <android.support.v4.view.ViewPager android:id="@+id/decrypt_pager" android:layout_width="match_parent" diff --git a/OpenKeychain/src/main/res/layout/import_keys_activity.xml b/OpenKeychain/src/main/res/layout/import_keys_activity.xml index 2a332823e..fc9d21e23 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_activity.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_activity.xml @@ -1,25 +1,58 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content_frame" android:layout_marginLeft="@dimen/drawer_content_padding" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:orientation="vertical"> - <FrameLayout - android:id="@+id/import_navigation_fragment" + <LinearLayout + android:id="@+id/card_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentTop="true" android:orientation="vertical" /> + <org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout + android:id="@+id/import_sliding_tab_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <android.support.v4.view.ViewPager + android:id="@+id/import_pager" + android:layout_width="match_parent" + android:layout_height="64dp" + android:background="@android:color/white" /> + + <View + android:layout_width="match_parent" + android:layout_height="2dip" + android:background="?android:attr/listDivider" /> + + <View + android:layout_width="match_parent" + android:layout_height="16dp" /> + + <View + android:layout_width="match_parent" + android:layout_height="2dip" + android:background="?android:attr/listDivider" /> + + <FrameLayout + android:id="@+id/import_keys_list_container" + android:layout_width="match_parent" + android:layout_height="0dp" + android:orientation="vertical" + android:layout_weight="1" + android:background="@android:color/white" /> + <LinearLayout android:id="@+id/import_footer" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentBottom="true" android:orientation="vertical" android:paddingLeft="16dp" - android:paddingRight="16dp"> + android:paddingRight="16dp" + android:background="@android:color/white"> <View android:layout_width="match_parent" @@ -43,16 +76,4 @@ style="@style/SelectableItem" /> </LinearLayout> - - <FrameLayout - android:id="@+id/import_keys_list_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_above="@+id/import_footer" - android:layout_alignParentLeft="true" - android:layout_below="@+id/import_navigation_fragment" - android:orientation="vertical" - android:paddingTop="8dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" /> -</RelativeLayout>
\ No newline at end of file +</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_clipboard_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_clipboard_fragment.xml deleted file mode 100644 index 739c34fba..000000000 --- a/OpenKeychain/src/main/res/layout/import_keys_clipboard_fragment.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:orientation="horizontal" > - - <com.beardedhen.androidbootstrap.BootstrapButton - android:id="@+id/import_clipboard_button" - android:layout_width="match_parent" - android:layout_height="70dp" - android:text="@string/import_clipboard_button" - bootstrapbutton:bb_icon_left="fa-clipboard" - bootstrapbutton:bb_size="default" - bootstrapbutton:bb_type="default" /> - -</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml index c07d2bb40..b1056dab3 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml @@ -1,21 +1,66 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="8dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" android:orientation="vertical"> - <com.beardedhen.androidbootstrap.BootstrapButton + <LinearLayout android:id="@+id/import_keys_file_browse" + android:paddingLeft="8dp" android:layout_width="match_parent" - android:layout_height="70dp" - android:text="@string/filemanager_title_open" - android:contentDescription="@string/filemanager_title_open" - bootstrapbutton:bb_icon_left="fa-folder-open" - bootstrapbutton:bb_size="default" - bootstrapbutton:bb_type="default" /> + android:layout_height="?android:attr/listPreferredItemHeight" + android:clickable="true" + style="@style/SelectableItem" + android:orientation="horizontal"> + + <TextView + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="0dip" + android:layout_height="match_parent" + android:text="@string/filemanager_title_open" + android:layout_weight="1" + android:drawableRight="@drawable/ic_action_collection" + android:drawablePadding="8dp" + android:gravity="center_vertical" /> + + <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/import_clipboard_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:padding="8dp" + android:src="@drawable/ic_action_paste" + android:layout_gravity="center_vertical" + style="@style/SelectableItem" /> + + </LinearLayout> + + <TextView + android:id="@+id/import_qrcode_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingTop="8dp" + android:visibility="gone" /> + + <ProgressBar + android:id="@+id/import_qrcode_progress" + style="?android:attr/progressBarStyleHorizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:progress="0" + android:visibility="gone" /> </LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml index ceba0e1ce..bf00b77e7 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml @@ -1,16 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="8dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" android:orientation="horizontal"> - <EditText android:id="@+id/import_keybase_query" + android:layout_marginLeft="8dp" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" @@ -22,17 +18,24 @@ android:lines="1" android:maxLines="1" android:minLines="1" + android:layout_marginRight="8dp" android:layout_gravity="center_vertical" /> - <com.beardedhen.androidbootstrap.BootstrapButton + <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/import_keybase_search" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="match_parent" + android:padding="16dp" + android:src="@drawable/ic_action_search" android:layout_gravity="center_vertical" - android:layout_marginLeft="8dp" - bootstrapbutton:bb_icon_left="fa-search" - bootstrapbutton:bb_roundedCorners="true" - bootstrapbutton:bb_size="default" - bootstrapbutton:bb_type="default" /> + style="@style/SelectableItem" /> </LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_list_entry.xml b/OpenKeychain/src/main/res/layout/import_keys_list_entry.xml index c91335a5b..56f34e2eb 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_list_entry.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_list_entry.xml @@ -17,9 +17,13 @@ <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="horizontal" - android:paddingRight="?android:attr/scrollbarSize" - android:singleLine="true"> + android:singleLine="true" + android:paddingLeft="8dp" + android:paddingRight="16dp" + android:paddingTop="4dp" + android:paddingBottom="4dp"> <CheckBox android:id="@+id/selected" diff --git a/OpenKeychain/src/main/res/layout/import_keys_nfc_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_nfc_fragment.xml deleted file mode 100644 index 8c0a80e4e..000000000 --- a/OpenKeychain/src/main/res/layout/import_keys_nfc_fragment.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:orientation="horizontal"> - - <com.beardedhen.androidbootstrap.BootstrapButton - android:id="@+id/import_nfc_button" - android:layout_width="wrap_content" - android:layout_height="70dp" - android:layout_alignParentRight="true" - android:layout_alignParentTop="true" - android:layout_marginLeft="8dp" - android:text="@string/import_nfc_help_button" - bootstrapbutton:bb_icon_left="fa-question" - bootstrapbutton:bb_size="default" - bootstrapbutton:bb_type="default" /> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:layout_toLeftOf="@+id/import_nfc_button" - android:text="@string/import_nfc_text" /> - -</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml index 590f7f797..09a31b4a8 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml @@ -1,21 +1,48 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto" - android:layout_width="fill_parent" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="8dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" android:orientation="vertical"> - <com.beardedhen.androidbootstrap.BootstrapButton + <LinearLayout android:id="@+id/import_qrcode_button" + android:paddingLeft="8dp" android:layout_width="match_parent" - android:layout_height="70dp" - android:text="@string/import_qr_scan_button" - bootstrapbutton:bb_icon_left="fa-barcode" - bootstrapbutton:bb_size="default" - bootstrapbutton:bb_type="default" /> + android:layout_height="?android:attr/listPreferredItemHeight" + android:clickable="true" + style="@style/SelectableItem" + android:orientation="horizontal"> + + <TextView + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="0dip" + android:layout_height="match_parent" + android:text="@string/import_qr_code_button" + android:layout_weight="1" + android:drawableRight="@drawable/ic_action_qr_code" + android:drawablePadding="8dp" + android:gravity="center_vertical" /> + + <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" /> + + <Button + android:id="@+id/import_nfc_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:padding="8dp" + android:text="NFC?" + android:layout_gravity="center_vertical" + style="@style/SelectableItem" /> + + </LinearLayout> <TextView android:id="@+id/import_qrcode_text" diff --git a/OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml index e17dbe783..7562eaa9b 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml @@ -1,20 +1,13 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto" + xmlns:custom="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingTop="8dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" + android:layout_height="wrap_content" android:orientation="vertical"> - <Spinner - android:id="@+id/import_server_spinner" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" + android:paddingLeft="8dp" + android:layout_height="?android:attr/listPreferredItemHeight" android:orientation="horizontal"> <EditText @@ -30,18 +23,63 @@ android:lines="1" android:maxLines="1" android:minLines="1" + android:layout_marginRight="8dp" android:layout_gravity="center_vertical" /> - <com.beardedhen.androidbootstrap.BootstrapButton + <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/import_server_search" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="match_parent" + android:padding="16dp" + android:src="@drawable/ic_action_search" + android:layout_gravity="center_vertical" + style="@style/SelectableItem" /> + + <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/import_server_config_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:padding="8dp" + android:src="@drawable/ic_action_settings" android:layout_gravity="center_vertical" + style="@style/SelectableItem" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/import_server_config" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> + + <Spinner + android:id="@+id/import_server_spinner" android:layout_marginLeft="8dp" - bootstrapbutton:bb_icon_left="fa-search" - bootstrapbutton:bb_roundedCorners="true" - bootstrapbutton:bb_size="default" - bootstrapbutton:bb_type="default" /> + android:layout_marginRight="8dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/log_display_activity.xml b/OpenKeychain/src/main/res/layout/log_display_activity.xml new file mode 100644 index 000000000..591e2650c --- /dev/null +++ b/OpenKeychain/src/main/res/layout/log_display_activity.xml @@ -0,0 +1,46 @@ +<?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"> + + <fragment + android:id="@+id/list" + android:name="org.sufficientlysecure.keychain.ui.LogDisplayFragment" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="0.9" + android:layout_marginRight="8dp" + android:layout_marginLeft="8dp" /> + + <LinearLayout + android:id="@+id/import_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingTop="8dp" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/import_import" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:layout_marginBottom="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:text="Close" + android:minHeight="?android:attr/listPreferredItemHeight" + android:gravity="center_vertical" + android:clickable="true" + style="@style/SelectableItem" /> + + </LinearLayout> + +</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 new file mode 100644 index 000000000..442e72d09 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/log_display_fragment.xml @@ -0,0 +1,11 @@ +<?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 new file mode 100644 index 000000000..35489afed --- /dev/null +++ b/OpenKeychain/src/main/res/layout/log_display_item.xml @@ -0,0 +1,22 @@ +<?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: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" /> +</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/notification_area.xml b/OpenKeychain/src/main/res/layout/notification_area.xml new file mode 100644 index 000000000..d1ba265a5 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/notification_area.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <LinearLayout + android:id="@+id/card_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/emphasis" + android:orientation="vertical" /> + +</merge>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_share_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_share_fragment.xml index 67c2e241a..1cd2b9f1b 100644 --- a/OpenKeychain/src/main/res/layout/view_key_share_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_share_fragment.xml @@ -24,8 +24,7 @@ <LinearLayout android:id="@+id/view_key_action_fingerprint_share" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeight" + android:layout_height="?android:attr/listPreferredItemHeight" android:clickable="true" style="@style/SelectableItem" android:orientation="horizontal"> @@ -63,7 +62,6 @@ </LinearLayout> - <View android:layout_width="match_parent" android:layout_height="1dip" @@ -90,8 +88,7 @@ <LinearLayout android:id="@+id/view_key_action_key_share" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeight" + android:layout_height="?android:attr/listPreferredItemHeight" android:clickable="true" style="@style/SelectableItem" android:orientation="horizontal"> @@ -135,8 +132,7 @@ <LinearLayout android:id="@+id/view_key_action_nfc_help" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeight" + android:layout_height="?android:attr/listPreferredItemHeight" android:clickable="true" style="@style/SelectableItem" android:orientation="horizontal" diff --git a/OpenKeychain/src/main/res/values-de/strings.xml b/OpenKeychain/src/main/res/values-de/strings.xml index e2dfa196b..d5cf037c8 100644 --- a/OpenKeychain/src/main/res/values-de/strings.xml +++ b/OpenKeychain/src/main/res/values-de/strings.xml @@ -206,23 +206,23 @@ <string name="ask_empty_id_ok">Es wurde eine leere IdentitƤt hinzugefügt. Wirklich fortfahren?</string> <string name="public_key_deletetion_confirmation">Soll der ƶffentliche Schlüssel \'%s\' wirklich gelƶscht werden?\nDies kann nicht rückgƤngig gemacht werden! </string> <string name="also_export_secret_keys">Private Schlüssel auch exportieren</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">%d Schlüssel erfolgreich hinzugefügt</item> <item quantity="other">%d Schlüssel erfolgreich hinzugefügt</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">und %d Schlüssel erfolgreich aktualisiert.</item> <item quantity="other">und %d Schlüssel erfolgreich aktualisiert.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">%d Schlüssel erfolgreich hinzugefügt.</item> <item quantity="other">%d Schlüssel erfolgreich hinzugefügt.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">%d Schlüssel erfolgreich aktualisiert.</item> <item quantity="other">%d Schlüssel erfolgreich aktualisiert.</item> </plurals> - <string name="no_keys_added_or_updated">Keine Schlüssel hinzugefügt oder aktualisiert.</string> + <string name="import_error_nothing">Keine Schlüssel hinzugefügt oder aktualisiert.</string> <string name="key_exported">1 Schlüssel erfolgreich exportiert.</string> <string name="keys_exported">%d Schlüssel erfolgreich exportiert.</string> <string name="no_keys_exported">Keine Schlüssel exportiert.</string> @@ -324,10 +324,8 @@ <string name="progress_verifying_integrity">IntegritƤt wird überprüftā¦</string> <string name="progress_deleting_securely">\'%s\' wird sicher gelƶschtā¦</string> <!--action strings--> - <string name="hint_public_keys">Ćffentliche Schlüssel suchen</string> <string name="hint_secret_keys">Private Schlüssel suchen</string> <string name="action_share_key_with">Teile Schlüssel überā¦</string> - <string name="hint_keybase_search">Durchsuche Keybase.io</string> <!--key bit length selections--> <string name="key_size_512">512</string> <string name="key_size_768">768</string> diff --git a/OpenKeychain/src/main/res/values-es/strings.xml b/OpenKeychain/src/main/res/values-es/strings.xml index 45d3d565b..f1e4e347d 100644 --- a/OpenKeychain/src/main/res/values-es/strings.xml +++ b/OpenKeychain/src/main/res/values-es/strings.xml @@ -206,23 +206,23 @@ <string name="ask_empty_id_ok">Ha aƱadido una identidad vacĆa, ĀæestĆ” seguro de que quiere continuar?</string> <string name="public_key_deletetion_confirmation">ĀæDe veras quiere borrar la clave pĆŗblica \'%s\'?\nĀ”No puede deshacer esto!</string> <string name="also_export_secret_keys">ĀæExportar tambiĆ©n las claves secretas?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">%d clave aƱadida satisfactoriamente</item> <item quantity="other">%d claves aƱadidas satisfactoriamente</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">y actualizada %d clave.</item> <item quantity="other">y actualizadas %d claves.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">%d clave aƱadida satisfactoriamente.</item> <item quantity="other">%d claves aƱadidas satisfactoriamente.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">%d clave actualizada satisfactoriamente.</item> <item quantity="other">%d claves actualizadas satisfactoriamente.</item> </plurals> - <string name="no_keys_added_or_updated">No se han aƱadido o actualizado claves.</string> + <string name="import_error_nothing">No se han aƱadido o actualizado claves.</string> <string name="key_exported">Se ha exportado 1 clave satisfactoriamente.</string> <string name="keys_exported">%d claves exportadas satisfactoriamente.</string> <string name="no_keys_exported">No se han exportado claves.</string> diff --git a/OpenKeychain/src/main/res/values-fr/strings.xml b/OpenKeychain/src/main/res/values-fr/strings.xml index f49127b6f..55a85fb9b 100644 --- a/OpenKeychain/src/main/res/values-fr/strings.xml +++ b/OpenKeychain/src/main/res/values-fr/strings.xml @@ -206,23 +206,23 @@ <string name="ask_empty_id_ok">Vous avez ajoutĆ© une identitĆ© vide, ĆŖtes-vous certain de vouloir continuer ?</string> <string name="public_key_deletetion_confirmation">Voulez-vous vraiment supprimer la clef publique %s ?\nCeci est irrĆ©versible !</string> <string name="also_export_secret_keys">Exporter aussi les clefs secrĆØtes ?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">%d clef ajoutĆ©e avec succĆØs</item> <item quantity="other">%d clefs ajoutĆ©es avec succĆØs</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">et %d clef mise Ć jour.</item> <item quantity="other">et %d clefs mises Ć jour.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">%d clef ajoutĆ©e avec succĆØs.</item> <item quantity="other">%d clefs ajoutĆ©es avec succĆØs.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">%d clef mise Ć jour avec succĆØs.</item> <item quantity="other">%d clefs mises Ć jour avec succĆØs.</item> </plurals> - <string name="no_keys_added_or_updated">Aucune clef ajoutĆ©e ou mise Ć jour.</string> + <string name="import_error_nothing">Aucune clef ajoutĆ©e ou mise Ć jour.</string> <string name="key_exported">1 clef exportĆ©e avec succĆØs.</string> <string name="keys_exported">%d clefs exportĆ©es avec succĆØs.</string> <string name="no_keys_exported">Aucune clef exportĆ©e.</string> diff --git a/OpenKeychain/src/main/res/values-it/strings.xml b/OpenKeychain/src/main/res/values-it/strings.xml index eae4dd4af..300627fa7 100644 --- a/OpenKeychain/src/main/res/values-it/strings.xml +++ b/OpenKeychain/src/main/res/values-it/strings.xml @@ -206,23 +206,23 @@ <string name="ask_empty_id_ok">Hai aggiunto una identitĆ vuota, sei sicuro di voler continuare?</string> <string name="public_key_deletetion_confirmation">Vuoi veramente eliminare la chiave pubblica \'%s\'?\nNon potrai annullare!</string> <string name="also_export_secret_keys">Esportare anche le chiavi segrete?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">%d chiave aggiunta correttamente</item> <item quantity="other">%d chiavi aggiunte correttamente</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">e %d chiave aggiornata.</item> <item quantity="other">e %d chiavi aggiornate.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">%d chiave aggiunta correttamente.</item> <item quantity="other">%d chiavi aggiunte correttamente.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">%d chiave aggiornata correttamente.</item> <item quantity="other">%d chiavi aggiornate correttamente.</item> </plurals> - <string name="no_keys_added_or_updated">Nessuna chiave aggiunta o aggiornata.</string> + <string name="import_error_nothing">Nessuna chiave aggiunta o aggiornata.</string> <string name="key_exported">1 chiave esportata correttamente.</string> <string name="keys_exported">%d chiavi esportate correttamente.</string> <string name="no_keys_exported">Nessuna chiave esportata.</string> diff --git a/OpenKeychain/src/main/res/values-ja/strings.xml b/OpenKeychain/src/main/res/values-ja/strings.xml index c40e9dbdc..63fef2af2 100644 --- a/OpenKeychain/src/main/res/values-ja/strings.xml +++ b/OpenKeychain/src/main/res/values-ja/strings.xml @@ -203,19 +203,19 @@ <string name="ask_empty_id_ok">ććŖććÆē©ŗć®ć¦ć¼ć¶IDćčæ½å ćć¾ććććć®ć¾ć¾ē¶ćć¾ćć?</string> <string name="public_key_deletetion_confirmation">å
¬ééµ\'%s\'ćę¬å½ć«åé¤ćć¦ćććć§ćć?\nćććÆå
ć«ę»ćć¾ćć!</string> <string name="also_export_secret_keys">ē§åÆéµććØćÆć¹ćć¼ććć¾ćć?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="other">%d ć®éµćčæ½å ćć¾ćć</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="other">ćć㦠%d ć®éµćć¢ćććć¼ććć¾ććć</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="other">%d ć®éµćčæ½å ćć¾ććć</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="other">%d ć®éµćć¢ćććć¼ććć¾ććć</item> </plurals> - <string name="no_keys_added_or_updated">éµć®čæ½å ććććÆę“ę°ćÆććć¾ććć§ććć</string> + <string name="import_error_nothing">éµć®čæ½å ććććÆę“ę°ćÆććć¾ććć§ććć</string> <string name="key_exported">1ć¤ć®éµććØćÆć¹ćć¼ććć¾ććć</string> <string name="keys_exported">%d ć®éµććØćÆć¹ćć¼ććć¾ććć</string> <string name="no_keys_exported">éµććØćÆć¹ćć¼ććć¦ćć¾ććć</string> diff --git a/OpenKeychain/src/main/res/values-nl/strings.xml b/OpenKeychain/src/main/res/values-nl/strings.xml index d35d83517..f75d7a166 100644 --- a/OpenKeychain/src/main/res/values-nl/strings.xml +++ b/OpenKeychain/src/main/res/values-nl/strings.xml @@ -206,23 +206,23 @@ <string name="ask_empty_id_ok">U heeft een lege identiteit toegevoegd, weet u zeker dat u wilt doorgaan?</string> <string name="public_key_deletetion_confirmation">Wilt u echt de publieke sleutel \'%s\' verwijderen?\nDit kunt u niet ongedaan maken!</string> <string name="also_export_secret_keys">Ook geheime sleutels exporteren?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">Succesvol %d sleutel toegevoegd</item> <item quantity="other">Succesvol %d sleutels toegevoegd</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">en %d sleutel bijgewerkt.</item> <item quantity="other">en %d sleutels bijgewerkt.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">Succesvol %d sleutel toegevoegd.</item> <item quantity="other">Succesvol %d sleutels toegevoegd.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">Succesvol %d sleutel bijgewerkt.</item> <item quantity="other">Succesvol %d sleutels bijgewerkt.</item> </plurals> - <string name="no_keys_added_or_updated">Geen sleutels toegevoegd of bijgewerkt.</string> + <string name="import_error_nothing">Geen sleutels toegevoegd of bijgewerkt.</string> <string name="key_exported">1 sleutel succesvol geĆ«xporteerd.</string> <string name="keys_exported">Succesvol %d sleutels geĆ«xporteerd.</string> <string name="no_keys_exported">Geen sleutels geĆ«xporteerd.</string> diff --git a/OpenKeychain/src/main/res/values-pl/strings.xml b/OpenKeychain/src/main/res/values-pl/strings.xml index d1b7de393..851e77c3a 100644 --- a/OpenKeychain/src/main/res/values-pl/strings.xml +++ b/OpenKeychain/src/main/res/values-pl/strings.xml @@ -191,27 +191,27 @@ <string name="ask_save_changed_key">ZostaÅy dokonane zmiany w pÄku kluczy, czy chcesz je zachowaÄ?</string> <string name="public_key_deletetion_confirmation">Czy na pewno chcesz usunÄ
Ä klucz publiczny \'%s\'?\nNie można cofnÄ
Ä tej operacji!</string> <string name="also_export_secret_keys">Czy wyeksportowaÄ również klucze prywatne?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">PomyÅlnie dodano %d klucz</item> <item quantity="few">PomyÅlnie dodano %d kluczy</item> <item quantity="other">PomyÅlnie dodano %d kluczy</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">i zaktualizowano %d klucz.</item> <item quantity="few">i zaktualizowano %d kluczy.</item> <item quantity="other">i zaktualizowano %d kluczy.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">PomyÅlnie dodano %d klucz.</item> <item quantity="few">PomyÅlnie dodano %d kluczy.</item> <item quantity="other">PomyÅlnie dodano %d kluczy.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">PomyÅlnie zaktualizowano %d klucz.</item> <item quantity="few">PomyÅlnie zaktualizowano %d kluczy.</item> <item quantity="other">PomyÅlnie zaktualizowano %d kluczy.</item> </plurals> - <string name="no_keys_added_or_updated">Nie dodano ani zaktualizowano żadnych kluczy.</string> + <string name="import_error_nothing">Nie dodano ani zaktualizowano żadnych kluczy.</string> <string name="key_exported">PomyÅlnie wyeksportowano 1 klucz.</string> <string name="keys_exported">PomyÅlnie wyeksportowano %d kluczy.</string> <string name="no_keys_exported">Nie wyeksportowano żadnych kluczy.</string> diff --git a/OpenKeychain/src/main/res/values-ru/strings.xml b/OpenKeychain/src/main/res/values-ru/strings.xml index 0becea0bc..b108324d1 100644 --- a/OpenKeychain/src/main/res/values-ru/strings.xml +++ b/OpenKeychain/src/main/res/values-ru/strings.xml @@ -206,27 +206,27 @@ <string name="ask_empty_id_ok">ŠŃ Гобавили ŠæŃŃŃŠ¾Š¹ ŠøŠ“ŠµŠ½ŃŠøŃŠøŠŗŠ°ŃŠ¾Ń. ŠŃ ŃŠ²ŠµŃенŃ, ŃŃŠ¾ Ń
Š¾ŃŠøŃе ŠæŃоГолжиŃŃ?</string> <string name="public_key_deletetion_confirmation">ŠŃ ŠæŃŠ°Š²Š“а Ń
Š¾ŃŠøŃе ŃŠ“алиŃŃ ŠæŃŠ±Š»ŠøŃŠ½ŃŠ¹ ŠŗŠ»ŃŃ \'%s\'?\nŠŃо ГейŃŃŠ²ŠøŠµ Š½ŠµŠ»ŃŠ·Ń Š¾ŃŠ¼ŠµŠ½ŠøŃŃ!</string> <string name="also_export_secret_keys">ŠŠŗŃпоŃŃŠøŃоваŃŃ ŃŠµŠŗŃŠµŃŠ½Ńе ŠŗŠ»ŃŃŠø?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">Š£ŃŠæŠµŃŠ½Š¾ Гобавлено %d ŠŗŠ»ŃŃ</item> <item quantity="few">Š£ŃŠæŠµŃŠ½Š¾ Гобавлено %d ŠŗŠ»ŃŃŠµŠ¹</item> <item quantity="other">Š£ŃŠæŠµŃŠ½Š¾ Гобавлено %d ŠŗŠ»ŃŃŠµŠ¹</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">Šø обновлен %d ŠŗŠ»ŃŃ.</item> <item quantity="few">Šø обновлено %d ŠŗŠ»ŃŃŠµŠ¹.</item> <item quantity="other">Šø обновлено %d ŠŗŠ»ŃŃŠµŠ¹.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">ŠŠ¾Š±Š°Š²Š»ŠµŠ½ %d ŠŗŠ»ŃŃ</item> <item quantity="few">ŠŠ¾Š±Š°Š²Š»ŠµŠ½Š¾ %d ŠŗŠ»ŃŃŠµŠ¹</item> <item quantity="other">ŠŠ¾Š±Š°Š²Š»ŠµŠ½Š¾ %d ŠŗŠ»ŃŃŠµŠ¹</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">ŠŠ±Š½Š¾Š²Š»ŠµŠ½ %d ŠŗŠ»ŃŃ.</item> <item quantity="few">ŠŠ±Š½Š¾Š²Š»ŠµŠ½Š¾ %d ŠŗŠ»ŃŃŠµŠ¹.</item> <item quantity="other">ŠŠ±Š½Š¾Š²Š»ŠµŠ½Š¾ %d ŠŗŠ»ŃŃŠµŠ¹.</item> </plurals> - <string name="no_keys_added_or_updated">ŠŠµŃ обновленнŃŃ
или ГобавленнŃŃ
ŠŗŠ»ŃŃŠµŠ¹</string> + <string name="import_error_nothing">ŠŠµŃ обновленнŃŃ
или ГобавленнŃŃ
ŠŗŠ»ŃŃŠµŠ¹</string> <string name="key_exported">Š£ŃŠæŠµŃŠ½Ńй ŃŠŗŃпоŃŃ 1 ŠŗŠ»ŃŃŠ°.</string> <string name="keys_exported">ŠŠŗŃпоŃŃŠøŃовано %d ŠŗŠ»ŃŃŠµŠ¹.</string> <string name="no_keys_exported">ŠŠ»ŃŃŠø не Š±Ńли ŃŠŗŃпоŃŃŠøŃованŃ.</string> diff --git a/OpenKeychain/src/main/res/values-sl/strings.xml b/OpenKeychain/src/main/res/values-sl/strings.xml index 8b12cdebe..0fe44725b 100644 --- a/OpenKeychain/src/main/res/values-sl/strings.xml +++ b/OpenKeychain/src/main/res/values-sl/strings.xml @@ -212,31 +212,31 @@ <string name="ask_empty_id_ok">Dodali ste prazno identiteto, ali res želite nadaljevati?</string> <string name="public_key_deletetion_confirmation">Ali res želite izbrisati javni kljuÄ \'%s\'?\nTega koraka ne boste mogli preklicati!</string> <string name="also_export_secret_keys">Želite izvoziti tudi zasebne kljuÄe?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">UspeÅ”no dodan %d kljuÄ</item> <item quantity="two">UspeÅ”no dodana %d kljuÄa</item> <item quantity="few">UspeÅ”no dodani %d kljuÄi</item> <item quantity="other">UspeÅ”no dodanih %d kljuÄev</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">in posodbljen %d.</item> <item quantity="two">in posodobljena %d.</item> <item quantity="few">in posodobljeni %d.</item> <item quantity="other">in posodobljenih %d.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">UspeÅ”no dodan %d kljuÄ.</item> <item quantity="two">UspeÅ”no dodana %d kljuÄa.</item> <item quantity="few">UspeÅ”no dodani %d kljuÄi.</item> <item quantity="other">UspeÅ”no dodanih %d kljuÄev.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">UspeÅ”no posodobljen %d kljuÄ.</item> <item quantity="two">UspeÅ”no posodobljena %d kljuÄa.</item> <item quantity="few">UspeÅ”no posodobljeni %d kljuÄi.</item> <item quantity="other">UspeÅ”no posodobljenih %d kljuÄev.</item> </plurals> - <string name="no_keys_added_or_updated">Noben kljuÄ ni bil dodan ali posodobljen.</string> + <string name="import_error_nothing">Noben kljuÄ ni bil dodan ali posodobljen.</string> <string name="key_exported">UspeÅ”no izvožen 1 kljuÄ.</string> <string name="keys_exported">UspeÅ”no izvoženih kljuÄev: %d</string> <string name="no_keys_exported">Noben kljuÄ ni bil izvožen.</string> diff --git a/OpenKeychain/src/main/res/values-uk/strings.xml b/OpenKeychain/src/main/res/values-uk/strings.xml index b27b6ffd3..2951d13f8 100644 --- a/OpenKeychain/src/main/res/values-uk/strings.xml +++ b/OpenKeychain/src/main/res/values-uk/strings.xml @@ -209,27 +209,27 @@ <string name="ask_empty_id_ok">ŠŠø вже ГоГали ŠæŠ¾ŃŠ¾Š¶Š½Ń ŃŃŃŠ½ŃŃŃŃ. ŠŠø ŃŠæŃŠ°Š²Š“Ń Ń
Š¾ŃŠµŃе ŠæŃŠ¾Š“Š¾Š²Š¶ŠøŃŠø?</string> <string name="public_key_deletetion_confirmation">ŠŠø ŃŠæŃŠ°Š²Š“Ń Ń
Š¾ŃŠµŃе вилŃŃŠøŃŠø Š²ŃŠ“ŠŗŃŠøŃий ŠŗŠ»ŃŃ \'%s\'?\nŠŠø не Š·Š¼Š¾Š¶ŠµŃе ŃŠµ Š²ŃŠ“Š¼ŃŠ½ŠøŃŠø!</string> <string name="also_export_secret_keys">Також ŠµŠŗŃпоŃŃŃŠ²Š°ŃŠø ŃŠµŠŗŃŠµŃŠ½Ń ŠŗŠ»ŃŃŃ?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">Š£ŃŠæŃŃŠ½Š¾ ГоГано %d ŠŗŠ»ŃŃ</item> <item quantity="few">Š£ŃŠæŃŃŠ½Š¾ ГоГано %d ŠŗŠ»ŃŃŃ</item> <item quantity="other">Š£ŃŠæŃŃŠ½Š¾ ГоГано %d ŠŗŠ»ŃŃŃŠ²</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">Ń Š¾Š½Š¾Š²Š»ŠµŠ½Š¾ %d ŠŗŠ»ŃŃ.</item> <item quantity="few">Ń Š¾Š½Š¾Š²Š»ŠµŠ½Š¾ %d ŠŗŠ»ŃŃŃ.</item> <item quantity="other">Ń Š¾Š½Š¾Š²Š»ŠµŠ½Š¾ %d ŠŗŠ»ŃŃŃŠ².</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">Š£ŃŠæŃŃŠ½Š¾ ГоГано %d ŠŗŠ»ŃŃ.</item> <item quantity="few">Š£ŃŠæŃŃŠ½Š¾ ГоГано %d ŠŗŠ»ŃŃŃ.</item> <item quantity="other">Š£ŃŠæŃŃŠ½Š¾ ГоГано %d ŠŗŠ»ŃŃŃŠ².</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">Š£ŃŠæŃŃŠ½Š¾ оновлено %d ŠŗŠ»ŃŃ.</item> <item quantity="few">Š£ŃŠæŃŃŠ½Š¾ оновлено %d ŠŗŠ»ŃŃŃ.</item> <item quantity="other">Š£ŃŠæŃŃŠ½Š¾ оновлено %d ŠŗŠ»ŃŃŃŠ².</item> </plurals> - <string name="no_keys_added_or_updated">ŠŠ¾Š“ного ŠŗŠ»ŃŃŠ° не ГоГано ŃŠ° не оновлено.</string> + <string name="import_error_nothing">ŠŠ¾Š“ного ŠŗŠ»ŃŃŠ° не ГоГано ŃŠ° не оновлено.</string> <string name="key_exported">Š£ŃŠæŃŃŠ½Š¾ ŠµŠŗŃŠæŠ¾ŃŃŠ¾Š²Š°Š½Š¾ 1 ŠŗŠ»ŃŃ.</string> <string name="keys_exported">Š£ŃŠæŃŃŠ½Š¾ ŠµŠŗŃŠæŠ¾ŃŃŠ¾Š²Š°Š½Š¾ %d ŠŗŠ»ŃŃŃŠ².</string> <string name="no_keys_exported">ŠŠ¾Š“ного ŠŗŠ»ŃŃŠ° не ŠµŠŗŃпоŃŃŠ¾Š²Š°Š½Š¾.</string> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 38f0519ac..ed9093194 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -28,6 +28,7 @@ <string name="title_certify_key">Certify Identities</string> <string name="title_key_details">Key Details</string> <string name="title_help">Help</string> + <string name="title_log_display">Log</string> <!-- section --> <string name="section_user_ids">Identities</string> @@ -74,16 +75,11 @@ <!-- menu --> <string name="menu_preferences">Settings</string> <string name="menu_help">Help</string> - <string name="menu_import_from_file">Import from file</string> - <string name="menu_import_from_qr_code">Import from QR Code</string> - <string name="menu_import_from_nfc">Import from NFC</string> <string name="menu_export_key">Export to file</string> <string name="menu_delete_key">Delete key</string> <string name="menu_create_key">Create key</string> <string name="menu_create_key_expert">Create key (expert)</string> <string name="menu_search">Search</string> - <string name="menu_import_from_key_server">Keyserver</string> - <string name="menu_import_from_keybase">Import from Keybase.io</string> <string name="menu_key_server">Keyserverā¦</string> <string name="menu_update_key">Update from keyserver</string> <string name="menu_export_key_to_server">Upload to key server</string> @@ -221,24 +217,6 @@ <string name="public_key_deletetion_confirmation">Do you really want to delete the public key \'%s\'?\nYou can\'t undo this!</string> <string name="also_export_secret_keys">Also export secret keys?</string> - <plurals name="keys_added_and_updated_1"> - <item quantity="one">Successfully added %d key</item> - <item quantity="other">Successfully added %d keys</item> - </plurals> - <plurals name="keys_added_and_updated_2"> - <item quantity="one"> and updated %d key.</item> - <item quantity="other"> and updated %d keys.</item> - </plurals> - <plurals name="keys_added"> - <item quantity="one">Successfully added %d key.</item> - <item quantity="other">Successfully added %d keys.</item> - </plurals> - <plurals name="keys_updated"> - <item quantity="one">Successfully updated %d key.</item> - <item quantity="other">Successfully updated %d keys.</item> - </plurals> - - <string name="no_keys_added_or_updated">No keys added or updated.</string> <string name="key_exported">Successfully exported 1 key.</string> <string name="keys_exported">Successfully exported %d keys.</string> <string name="no_keys_exported">No keys exported.</string> @@ -246,11 +224,6 @@ <string name="key_creation_weak_rsa_info">Note: generating RSA key with length 1024-bit and less is considered unsafe and it\'s disabled for generating new keys.</string> <string name="key_not_found">Couldn\'t find key %08X.</string> - <plurals name="keys_found"> - <item quantity="one">Found %d key.</item> - <item quantity="other">Found %d keys.</item> - </plurals> - <plurals name="bad_keys_encountered"> <item quantity="one">%d bad secret key ignored. Perhaps you exported with the option\n --export-secret-subkeys\nMake sure you export with\n --export-secret-keys\ninstead.</item> <item quantity="other">%d bad secret keys ignored. Perhaps you exported with the option\n --export-secret-subkeys\nMake sure you export with\n --export-secret-keys\ninstead.</item> @@ -295,11 +268,11 @@ <string name="error_only_files_are_supported">Direct binary data without actual file in filesystem is not supported.</string> <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">Nothing to import!</string> + <string name="error_nothing_import">No keys found!</string> <string name="error_keyserver_insufficient_query">Key search query too short</string> <string name="error_searching_keys">Unrecoverable error searching for keys at server</string> <string name="error_keyserver_too_many_responses">Key search query returned too many candidates; Please refine query</string> - <string name="error_import_file_no_content">File has no content</string> + <string name="error_import_file_no_content">File/Clipboard is empty</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> @@ -356,10 +329,10 @@ <string name="progress_deleting_securely">deleting \'%s\' securelyā¦</string> <!-- action strings --> - <string name="hint_public_keys">Search Public Keys</string> + <string name="hint_public_keys">Name/Email/Key IDā¦</string> <string name="hint_secret_keys">Search Secret Keys</string> <string name="action_share_key_with">Share Key withā¦</string> - <string name="hint_keybase_search">Search Keybase.io</string> + <string name="hint_keybase_search">Name/Keybase.io usernameā¦</string> <!-- key bit length selections --> <string name="key_size_512">512</string> @@ -389,6 +362,10 @@ <string name="help_about_version">Version:</string> <!-- Import --> + <string name="import_tab_keyserver">Keyserver</string> + <string name="import_tab_direct">File/Clipboard</string> + <string name="import_tab_qr_code">QR Code/NFC</string> + <string name="import_tab_keybase">Keybase.io</string> <string name="import_import">Import selected keys</string> <string name="import_from_clipboard">Import from clipboard</string> @@ -404,9 +381,32 @@ <string name="import_qr_scan_button">Scan QR Code with \'Barcode Scanner\'</string> <string name="import_nfc_text">To receive keys via NFC, the device needs to be unlocked.</string> <string name="import_nfc_help_button">Help</string> + <string name="import_qr_code_button">Scan QR Codeā¦</string> <string name="import_clipboard_button">Get key from clipboard</string> <string name="import_keybase_button">Get key from Keybase.io</string> + <!-- Import result toast --> + <plurals name="import_keys_added_and_updated_1"> + <item quantity="one">Successfully added %1$d key</item> + <item quantity="other">Successfully added %1$d keys</item> + </plurals> + <plurals name="import_keys_added_and_updated_2"> + <item quantity="one"> and updated %1$d key%2$s.</item> + <item quantity="other"> and updated %1$d keys%2$s.</item> + </plurals> + <plurals name="import_keys_added"> + <item quantity="one">Successfully added %1$d key%2$s.</item> + <item quantity="other">Successfully added %1$d keys%2$s.</item> + </plurals> + <plurals name="import_keys_updated"> + <item quantity="one">Successfully updated %1$d key%2$s.</item> + <item quantity="other">Successfully updated %1$d keys%2$s.</item> + </plurals> + <string name="import_view_log">View Log</string> + <string name="import_error_nothing">Nothing to import.</string> + <string name="import_error">Error importing keys!</string> + <string name="import_with_warnings">, with warnings</string> + <!-- Intent labels --> <string name="intent_decrypt_file">Decrypt File with OpenKeychain</string> <string name="intent_import_key">Import Key with OpenKeychain</string> @@ -500,6 +500,114 @@ <string name="cert_verify_error">error!</string> <string name="cert_verify_unavailable">key unavailable</string> + <!-- Import Public log entries --> + <string name="msg_ip_apply_batch">Applying insert batch operation.</string> + <string name="msg_ip_bad_type_secret">Tried to import secret keyring as public. This is a bug, please file a report!</string> + <string name="msg_ip_delete_old_fail">No old key deleted (creating a new one?)</string> + <string name="msg_ip_delete_old_ok">Deleted old key from database</string> + <string name="msg_ip_encode_fail">Operation failed due to encoding error</string> + <string name="msg_ip_fail_io_exc">Operation failed due to i/o error</string> + <string name="msg_ip_fail_op_exc">Operation failed due to database error</string> + <string name="msg_ip_fail_remote_ex">Operation failed due to internal error</string> + <string name="msg_ip">Importing public keyring %s</string> + <string name="msg_ip_insert_keyring">Encoding keyring data</string> + <string name="msg_ip_insert_keys">Parsing keys</string> + <string name="msg_ip_prepare">Preparing database operations</string> + <string name="msg_ip_master">Processing master key %s</string> + <string name="msg_ip_master_expired">Keyring expired on %s</string> + <string name="msg_ip_master_expires">Keyring expires on %s</string> + <string name="msg_ip_master_flags_ces">Master key flags: certify, encrypt, sign</string> + <string name="msg_ip_master_flags_cex">Master key flags: certify, encrypt</string> + <string name="msg_ip_master_flags_cxs">Master key flags: certify, sign</string> + <string name="msg_ip_master_flags_xes">Master key flags: encrypt, sign</string> + <string name="msg_ip_master_flags_cxx">Master key flags: certify</string> + <string name="msg_ip_master_flags_xex">Master key flags: encrypt</string> + <string name="msg_ip_master_flags_xxs">Master key flags: sign</string> + <string name="msg_ip_master_flags_xxx">Master key flags: none</string> + <string name="msg_ip_subkey">Processing subkey %s</string> + <string name="msg_ip_subkey_expired">Subkey expired on %s</string> + <string name="msg_ip_subkey_expires">Subkey expires on %s</string> + <string name="msg_ip_subkey_flags_ces">Subkey flags: certify, encrypt, sign</string> + <string name="msg_ip_subkey_flags_cex">Subkey flags: certify, encrypt</string> + <string name="msg_ip_subkey_flags_cxs">Subkey flags: certify, sign</string> + <string name="msg_ip_subkey_flags_xes">Subkey flags: encrypt, sign</string> + <string name="msg_ip_subkey_flags_cxx">Subkey flags: certify</string> + <string name="msg_ip_subkey_flags_xex">Subkey flags: encrypt</string> + <string name="msg_ip_subkey_flags_xxs">Subkey flags: sign</string> + <string name="msg_ip_subkey_flags_xxx">Subkey flags: none</string> + <string name="msg_ip_success">Successfully imported public keyring</string> + <string name="msg_ip_success_identical">Keyring contains no new data, nothing to do</string> + <string name="msg_ip_reinsert_secret">Re-inserting secret key</string> + <string name="msg_ip_uid_cert_bad">Encountered bad certificate!</string> + <string name="msg_ip_uid_cert_error">Error processing certificate!</string> + <string name="msg_ip_uid_cert_good">User id is certified by %1$s (%2$s)</string> + <string name="msg_ip_uid_certs_unknown">Ignoring %s certificates from unknown pubkeys</string> + <string name="msg_ip_uid_classifying">Classifying user ids, using %s trusted signatures</string> + <string name="msg_ip_uid_reorder">Re-ordering user ids</string> + <string name="msg_ip_uid_processing">Processing user id %s</string> + <string name="msg_ip_uid_revoked">User id is revoked</string> + <string name="msg_is_bad_type_public">Tried to import public keyring as secret. This is a bug, please file a report!</string> + + <!-- Import Secret log entries --> + <string name="msg_is">Importing secret key %s</string> + <string name="msg_is_db_exception">Database error!</string> + <string name="msg_is_importing_subkeys">Processing secret subkeys</string> + <string name="msg_is_io_exc">Error encoding keyring</string> + <string name="msg_is_subkey_nonexistent">Subkey %s unavailable in public key</string> + <string name="msg_is_subkey_ok">Marked %s as available</string> + <string name="msg_is_subkey_stripped">Marked %s as stripped</string> + <string name="msg_is_success">Successfully imported secret keyring</string> + <string name="msg_is_success_identical">Keyring contains no new data, nothing to do</string> + + <!-- Keyring Canonicalization log entries --> + <string name="msg_kc_public">Canonicalizing public keyring %s</string> + <string name="msg_kc_secret">Canonicalizing secret keyring %s</string> + <string name="msg_kc_fatal_no_uid">Keyring canonicalization failed: Keyring has no valid user ids</string> + <string name="msg_kc_master">Processing master key</string> + <string name="msg_kc_revoke_bad_err">Removing bad keyring revocation certificate</string> + <string name="msg_kc_revoke_bad_local">Removing keyring revocation certificate with "local" flag</string> + <string name="msg_kc_revoke_bad_time">Removing keyring revocation certificate with future timestamp</string> + <string name="msg_kc_revoke_bad_type">Removing master key certificate of unknown type (%s)</string> + <string name="msg_kc_revoke_bad">Removing bad keyring revocation certificate</string> + <string name="msg_kc_revoke_dup">Removing redundant keyring revocation certificate</string> + <string name="msg_kc_sub">Processing subkey %s</string> + <string name="msg_kc_sub_bad">Removing invalid subkey binding certificate</string> + <string name="msg_kc_sub_bad_err">Removing bad subkey binding certificate</string> + <string name="msg_kc_sub_bad_local">Removing subkey binding certificate with "local" flag</string> + <string name="msg_kc_sub_bad_keyid">Subkey binding issuer id mismatch</string> + <string name="msg_kc_sub_bad_time">Removing subkey binding certificate with future timestamp</string> + <string name="msg_kc_sub_bad_type">Unknown subkey certificate type: %s</string> + <string name="msg_kc_sub_primary_bad">Removing subkey binding certificate due to invalid primary binding certificate</string> + <string name="msg_kc_sub_primary_bad_err">Removing subkey binding certificate due to bad primary binding certificate</string> + <string name="msg_kc_sub_primary_none">Removing subkey binding certificate due to missing primary binding certificate</string> + <string name="msg_kc_sub_no_cert">No valid certificate found for %s, removing from ring</string> + <string name="msg_kc_sub_revoke_bad_err">Removing bad subkey revocation key</string> + <string name="msg_kc_sub_revoke_bad">Removing bad subkey revocation key</string> + <string name="msg_kc_sub_revoke_dup">Removing redundant keyring revocation key</string> + <string name="msg_kc_success">Keyring canonicalization successful, no changes</string> + <string name="msg_kc_success_bad">Keyring canonicalization successful, removed %s erroneous certificates</string> + <string name="msg_kc_success_bad_and_red">Keyring canonicalization successful, removed %1$s erroneous and %2$s redundant certificates</string> + <string name="msg_kc_success_redundant">Keyring canonicalization successful, removed %s redundant certificates</string> + <string name="msg_kc_uid_bad_err">Removing bad self certificate for user id %s</string> + <string name="msg_kc_uid_bad_local">Removing user id certificate with "local" flag</string> + <string name="msg_kc_uid_bad_time">Removing user id with future timestamp</string> + <string name="msg_kc_uid_bad_type">Removing user id certificate of unknown type (%s)</string> + <string name="msg_kc_uid_bad">Removing bad self certificate for user id "%s"</string> + <string name="msg_kc_uid_dup">Removing outdated self certificate for user id "%s"</string> + <string name="msg_kc_uid_foreign">Removing foreign user id certificate by %s</string> + <string name="msg_kc_uid_revoke_dup">Removing redundant revocation certificate for user id "%s"</string> + <string name="msg_kc_uid_revoke_old">Removing outdated revocation certificate for user id "%s"</string> + <string name="msg_kc_uid_no_cert">No valid self-certificate found for user id %s, removing from ring</string> + + + <!-- Keyring merging log entries --> + <string name="msg_mg_public">Merging into public keyring %s</string> + <string name="msg_mg_secret">Merging into secret keyring %s</string> + <string name="msg_mg_fatal_encode">Fatal error encoding signature</string> + <string name="msg_mg_heterogeneous">Tried to consolidate heterogeneous keyrings</string> + <string name="msg_mg_new_subkey">Adding new subkey %s</string> + <string name="msg_mg_found_new">Found %s new certificates in keyring</string> + <!-- unsorted --> <string name="section_certifier_id">Certifier</string> <string name="section_cert">Certificate Details</string> @@ -523,43 +631,4 @@ <string name="info_no_manual_account_creation">Do not create OpenKeychain-Accounts manually.\nFor more information, see Help.</string> <string name="contact_show_key">Show key (%s)</string> - <!-- Import log entries --> - <string name="msg_ip_apply_batch">Applying insert batch operation.</string> - <string name="msg_ip_bad_type_secret">Tried to import secret keyring as public. This is a bug, please file a report!</string> - <string name="msg_ip_delete_old_fail">No old key deleted (creating a new one?)</string> - <string name="msg_ip_delete_old_ok">Deleted old key from database</string> - <string name="msg_ip_encode_fail">Operation failed due to encoding error</string> - <string name="msg_ip_fail_io_exc">Operation failed due to i/o error</string> - <string name="msg_ip_fail_op_ex">Operation failed due to database error</string> - <string name="msg_ip_fail_remote_ex">Operation failed due to internal error</string> - <string name="msg_ip_importing">Importing public keyring</string> - <string name="msg_ip_insert_keyring">Inserting keyring data</string> - <string name="msg_ip_insert_subkey">Inserting subkey %s</string> - <string name="msg_ip_insert_subkeys">Inserting subkeys</string> - <string name="msg_ip_preserving_secret">Preserving available secret key</string> - <string name="msg_ip_reinsert_secret">Re-inserting secret key</string> - <string name="msg_ip_success">Successfully inserted public keyring</string> - <string name="msg_ip_trust_retrieve">Retrieving trusted keys</string> - <string name="msg_ip_trust_using">Using %s trusted keys</string> - <string name="msg_ip_trust_using_sec">Secret key available, self certificates are trusted</string> - <string name="msg_ip_uid_cert_bad">Encountered bad certificate!</string> - <string name="msg_ip_uid_cert_error">Error processing certificate!</string> - <string name="msg_ip_uid_cert_good">Found good certificate from %s</string> - <string name="msg_ip_uid_certs_unknown">Ignored %s certificates from unknown pubkeys</string> - <string name="msg_ip_uid_classifying">Classifying user ids</string> - <string name="msg_ip_uid_insert">Inserting user ids</string> - <string name="msg_ip_uid_processing">Processing user id %s</string> - <string name="msg_ip_uid_self_bad">Bad self certificate encountered!</string> - <string name="msg_ip_uid_self_good">Found good self certificate</string> - <string name="msg_ip_uid_self_ignoring_old">Ignoring older self certificate</string> - <string name="msg_ip_uid_self_newer">Using more recent good self certificate</string> - <string name="msg_is_bad_type_public">Tried to import public keyring as secret. This is a bug, please file a report!</string> - <string name="msg_is_importing">Importing secret key %s</string> - <string name="msg_is_importing_subkeys">Processing secret subkeys</string> - <string name="msg_is_io_excption">Error encoding keyring</string> - <string name="msg_is_subkey_nonexistent">Subkey %s unavailable in public key</string> - <string name="msg_is_subkey_ok">Marked %s as available</string> - <string name="msg_is_subkey_stripped">Marked %s as stripped</string> - <string name="msg_is_success">Successfully inserted secret keyring</string> - </resources> @@ -33,7 +33,7 @@ Development mailinglist at http://groups.google.com/d/forum/openpgp-keychain-dev 1. Get all external submodules with ``git submodule update --init --recursive`` 2. Have Android SDK "tools", "platform-tools", and "build-tools" directories in your PATH (http://developer.android.com/sdk/index.html) 3. Open the Android SDK Manager (shell command: ``android``). -Expand the Tools directory and select "Android SDK Build-tools (Version 19.0.3)". +Expand the Tools directory and select "Android SDK Build-tools (Version 19.1)". Expand the Extras directory and install "Android Support Repository" Select everything for the newest SDK Platform (API-Level 19) 4. Export ANDROID_HOME pointing to your Android SDK @@ -42,8 +42,8 @@ Select everything for the newest SDK Platform (API-Level 19) ### Build API Demo with Gradle -1. Follow 1-3 from above -2. Change to API Demo directory ``cd OpenKeychain-API`` +1. Follow 1-4 from above +2. The example code is available at https://github.com/open-keychain/api-example 3. Execute ``./gradlew build`` ### Development with Android Studio @@ -227,8 +227,11 @@ Some parts and some libraries are Apache License v2, MIT X11 License (see below) * icon.svg modified version of kgpg_key2_kopete.svgz -* Menu icons +* Actionbar icons http://developer.android.com/design/downloads/index.html#action-bar-icon-pack + +* QR Code Actionbar icon + https://github.com/openintents/openintents/blob/master/extensions/qrcode_ext/icons/ic_menu_qr_code/ic_menu_qr_code_holo_light/ic_menu_qr_code.svg * Purple color scheme http://android-holo-colors.com/ diff --git a/Resources/graphics/ic_action_qr_code.png b/Resources/graphics/ic_action_qr_code.png Binary files differnew file mode 100644 index 000000000..3c311c7df --- /dev/null +++ b/Resources/graphics/ic_action_qr_code.png diff --git a/Resources/graphics/ic_action_qr_code.svg b/Resources/graphics/ic_action_qr_code.svg new file mode 100644 index 000000000..da7d0f97c --- /dev/null +++ b/Resources/graphics/ic_action_qr_code.svg @@ -0,0 +1,753 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Layer_1" + x="0px" + y="0px" + width="48px" + height="48px" + viewBox="0 0 48 48" + enable-background="new 0 0 48 48" + xml:space="preserve" + inkscape:version="0.48.3.1 r9886" + sodipodi:docname="ic_menu_qr_code.svg"><metadata + id="metadata231"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs229"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +</defs><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1918" + inkscape:window-height="1179" + id="namedview227" + showgrid="false" + inkscape:zoom="8.4558186" + inkscape:cx="-17.080652" + inkscape:cy="14.446251" + inkscape:window-x="0" + inkscape:window-y="19" + inkscape:window-maximized="1" + inkscape:current-layer="Layer_1" /> +<rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect25" + height="5.0662165" + width="1.6889851" + y="9.6451044" + x="9.6451044" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect39" + height="5.0662165" + width="1.6889851" + y="9.6451044" + x="11.334089" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect55" + height="5.0662165" + width="1.6882463" + y="9.6451044" + x="13.023813" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect33" + height="5.0662165" + width="1.6889851" + y="33.287201" + x="9.6451044" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect49" + height="5.0662165" + width="1.6889851" + y="33.287201" + x="11.334089" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect61" + height="5.0662165" + width="1.6882463" + y="33.287201" + x="13.023813" /><rect + y="6.2678733" + x="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect5" + height="11.821418" + width="1.6882463" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect13" + height="1.6882463" + width="1.6889851" + x="7.9561195" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect15" + height="1.6889851" + width="1.6889851" + y="16.400307" + x="7.9561195" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect23" + height="1.6882463" + width="1.6889851" + x="9.6451044" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect27" + height="1.6889851" + width="1.6889851" + y="16.400307" + x="9.6451044" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect37" + height="1.6882463" + width="1.6889851" + x="11.334089" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect41" + height="1.6889851" + width="1.6889851" + y="16.400307" + x="11.334089" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect53" + height="1.6882463" + width="1.6882463" + x="13.023813" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect57" + height="1.6889851" + width="1.6882463" + y="16.400307" + x="13.023813" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect65" + height="1.6882463" + width="1.6882463" + x="14.712059" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect67" + height="1.6889851" + width="1.6882463" + y="16.400307" + x="14.712059" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect75" + height="11.821418" + width="1.6889851" + x="16.400307" /><rect + x="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect11" + height="11.821418" + width="1.6882463" + y="29.910709" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect19" + height="1.6882463" + width="1.6889851" + y="29.910709" + x="7.9561195" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect21" + height="1.6889851" + width="1.6889851" + y="40.04314" + x="7.9561195" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect31" + height="1.6882463" + width="1.6889851" + y="29.910709" + x="9.6451044" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect35" + height="1.6889851" + width="1.6889851" + y="40.04314" + x="9.6451044" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect47" + height="1.6882463" + width="1.6889851" + y="29.910709" + x="11.334089" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect51" + height="1.6889851" + width="1.6889851" + y="40.04314" + x="11.334089" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect59" + height="1.6882463" + width="1.6882463" + y="29.910709" + x="13.023813" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect63" + height="1.6889851" + width="1.6882463" + y="40.04314" + x="13.023813" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect71" + height="1.6882463" + width="1.6882463" + y="29.910709" + x="14.712059" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect73" + height="1.6889851" + width="1.6882463" + y="40.04314" + x="14.712059" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect83" + height="11.821418" + width="1.6889851" + y="29.910709" + x="16.400307" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect163" + height="5.0662165" + width="1.6897238" + y="9.6451044" + x="33.287201" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect177" + height="5.0662165" + width="1.6882463" + y="9.6451044" + x="34.976925" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect191" + height="5.0662165" + width="1.6897238" + y="9.6451044" + x="36.665913" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect141" + height="11.821418" + width="1.6882463" + x="29.910709" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect151" + height="1.6882463" + width="1.6882463" + x="31.598955" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect153" + height="1.6889851" + width="1.6882463" + y="16.400307" + x="31.598955" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect161" + height="1.6882463" + width="1.6897238" + x="33.287201" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect165" + height="1.6889851" + width="1.6897238" + y="16.400307" + x="33.287201" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect175" + height="1.6882463" + width="1.6882463" + x="34.976925" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect179" + height="1.6889851" + width="1.6882463" + y="16.400307" + x="34.976925" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect189" + height="1.6882463" + width="1.6897238" + x="36.665913" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect193" + height="1.6889851" + width="1.6897238" + y="16.400307" + x="36.665913" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect203" + height="1.6882463" + width="1.6875074" + x="38.355633" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect205" + height="1.6889851" + width="1.6875074" + y="16.400307" + x="38.355633" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect217" + height="11.821418" + width="1.6889851" + x="40.04314" /><rect + x="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect7" + height="3.3779702" + width="1.6882463" + y="19.777536" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect17" + height="1.6897238" + width="1.6889851" + y="26.53274" + x="7.9561195" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect29" + height="5.0654774" + width="1.6889851" + y="21.466522" + x="9.6451044" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect43" + height="1.6889851" + width="1.6889851" + y="21.466522" + x="11.334089" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect45" + height="3.3779702" + width="1.6889851" + y="24.844492" + x="11.334089" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect69" + height="1.6889851" + width="1.6882463" + y="23.155508" + x="14.712059" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect77" + height="1.6889851" + width="1.6889851" + y="19.777536" + x="16.400307" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect79" + height="1.6889851" + width="1.6889851" + y="23.155508" + x="16.400307" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect81" + height="1.6897238" + width="1.6889851" + y="26.53274" + x="16.400307" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect85" + height="3.3779702" + width="1.6889851" + y="21.466522" + x="18.089291" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect87" + height="5.0662165" + width="1.6889851" + y="7.9561195" + x="19.778276" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect89" + height="13.510403" + width="1.6889851" + y="16.400307" + x="19.778276" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect91" + height="1.6889851" + width="1.6889851" + y="40.04314" + x="19.778276" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect93" + height="1.6882463" + width="1.6882463" + x="21.467262" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect95" + height="1.6889851" + width="1.6882463" + y="13.023074" + x="21.467262" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect97" + height="5.0662165" + width="1.6882463" + y="24.844492" + x="21.467262" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect99" + height="1.6882463" + width="1.6882463" + y="31.598955" + x="21.467262" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect101" + height="6.7552013" + width="1.6882463" + y="34.976925" + x="21.467262" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect103" + height="1.6882463" + width="1.6889851" + y="11.334089" + x="23.155508" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect107" + height="5.0662165" + width="1.6889851" + y="19.777536" + x="23.155508" /><rect + y="6.2678733" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect113" + height="1.6882463" + width="1.6882463" + x="24.844492" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect117" + height="1.6882463" + width="1.6882463" + y="18.089291" + x="24.844492" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect119" + height="1.6889851" + width="1.6882463" + y="23.155508" + x="24.844492" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect121" + height="1.6882463" + width="1.6882463" + y="28.222464" + x="24.844492" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect123" + height="1.6882463" + width="1.6882463" + y="31.598955" + x="24.844492" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect125" + height="1.6889851" + width="1.6882463" + y="40.04314" + x="24.844492" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect127" + height="3.3779702" + width="1.6889851" + y="7.9561195" + x="26.53274" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect129" + height="10.133172" + width="1.6889851" + y="13.023074" + x="26.53274" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect131" + height="8.4434481" + width="1.6889851" + y="28.222464" + x="26.53274" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect135" + height="3.3779702" + width="1.6889851" + y="26.53274" + x="28.221725" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect137" + height="1.6882463" + width="1.6889851" + y="31.598955" + x="28.221725" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect139" + height="1.6889851" + width="1.6889851" + y="40.04314" + x="28.221725" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect145" + height="1.6889851" + width="1.6882463" + y="23.155508" + x="29.910709" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect149" + height="1.6897238" + width="1.6882463" + y="36.665913" + x="29.910709" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect155" + height="5.0654774" + width="1.6882463" + y="21.466522" + x="31.598955" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect157" + height="3.3779702" + width="1.6882463" + y="31.598955" + x="31.598955" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect169" + height="1.6897238" + width="1.6897238" + y="26.53274" + x="33.287201" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect171" + height="3.3764925" + width="1.6897238" + y="29.910709" + x="33.287201" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect173" + height="6.7552013" + width="1.6897238" + y="34.976925" + x="33.287201" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect181" + height="1.6889851" + width="1.6882463" + y="19.777536" + x="34.976925" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect183" + height="3.3764925" + width="1.6882463" + y="23.155508" + x="34.976925" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect185" + height="1.6882463" + width="1.6882463" + y="31.598955" + x="34.976925" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect187" + height="1.6875074" + width="1.6882463" + y="38.355633" + x="34.976925" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect195" + height="6.7552013" + width="1.6897238" + y="19.777536" + x="36.665913" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect197" + height="1.6882463" + width="1.6897238" + y="28.222464" + x="36.665913" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect199" + height="1.6882463" + width="1.6897238" + y="31.598955" + x="36.665913" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect201" + height="5.0647388" + width="1.6897238" + y="34.976925" + x="36.665913" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect207" + height="1.6889851" + width="1.6875074" + y="19.777536" + x="38.355633" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect209" + height="5.0662165" + width="1.6875074" + y="23.155508" + x="38.355633" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect211" + height="1.6882463" + width="1.6875074" + y="29.910709" + x="38.355633" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect215" + height="1.6889851" + width="1.6875074" + y="40.04314" + x="38.355633" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect219" + height="1.6882463" + width="1.6889851" + y="24.844492" + x="40.04314" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect223" + height="3.3779702" + width="1.6889851" + y="31.598955" + x="40.04314" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect225" + height="1.6897238" + width="1.6889851" + y="36.665913" + x="40.04314" /> +</svg>
\ No newline at end of file diff --git a/Resources/graphics/update-icon.sh b/Resources/graphics/update-icon.sh index 3df587a21..05b80e6b3 100755 --- a/Resources/graphics/update-icon.sh +++ b/Resources/graphics/update-icon.sh @@ -1,6 +1,6 @@ #!/bin/bash -APP_DIR=../../OpenPGP-Keychain/src/main +APP_DIR=../../OpenKeychain/src/main LDPI_DIR=$APP_DIR/res/drawable-ldpi MDPI_DIR=$APP_DIR/res/drawable-mdpi HDPI_DIR=$APP_DIR/res/drawable-hdpi diff --git a/build.gradle b/build.gradle index 815a8ff9a..e3cafe3ce 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { dependencies { // NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information - classpath 'com.android.tools.build:gradle:0.10.0' - classpath 'org.robolectric.gradle:gradle-android-test-plugin:0.10.0' + classpath 'com.android.tools.build:gradle:0.11.1' + //classpath 'org.robolectric.gradle:gradle-android-test-plugin:0.10.0' } } @@ -17,5 +17,5 @@ allprojects { } task wrapper(type: Wrapper) { - gradleVersion = '1.10' + gradleVersion = '1.12' } diff --git a/extern/AndroidBootstrap b/extern/AndroidBootstrap -Subproject bfa160c4ef3a1a53aebd68aca9c05b8e546219e +Subproject 02f02391e3eee9331e07d7690d3b533a8b0f69e diff --git a/extern/AppMsg b/extern/AppMsg -Subproject ca714df97bfce67a7a9a1efefd2c49645b6f22e +Subproject 61e74741909a712db2e0d31ddffa5b7cf37c21f diff --git a/extern/StickyListHeaders b/extern/StickyListHeaders -Subproject efe46c21143cc54a2394303a67822f14580d1d2 +Subproject 706c0e447229226b6edc82ab10630d39fd0f6c3 diff --git a/extern/SuperToasts b/extern/SuperToasts new file mode 160000 +Subproject 8578cfe6917cf16a9f123c1964e4bbff2a15be5 diff --git a/extern/html-textview b/extern/html-textview -Subproject c31ef2aff4282ad00af98e879e3e0a6000885b5 +Subproject eedaa334e761273efbfc49ded2124df58c8a4d8 diff --git a/extern/openkeychain-api-lib b/extern/openkeychain-api-lib -Subproject 26497acb27e9f6349c0557b15cd24a5b0b735e7 +Subproject 175a3cb772c88c9b50985abc98f81c9ea69c365 diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib -Subproject 650e1ebda82596cd4fbfaae406e6eccf189f4f6 +Subproject a77887d32fae68171fcd0d2989bf537c0c11f0b diff --git a/extern/zxing-android-integration b/extern/zxing-android-integration -Subproject 34029d4dcac81ec06137a046a189dac608e76ef +Subproject 1d787845663fd232f98f5e8e0923733c1a188f2 diff --git a/extern/zxing-qr-code b/extern/zxing-qr-code -Subproject 50193905fd8cef92140ea242f77b04bb31391c9 +Subproject 9ef2f3b66ea7cc283e865ec39434d023a18d17f diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d8e0b5b29..cc3d5d9a3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Mar 06 22:23:44 CET 2014 +#Mon Jun 09 22:04:23 CEST 2014 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-bin.zip diff --git a/settings.gradle b/settings.gradle index d7ca15f82..282c8a234 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,4 +11,5 @@ include ':extern:spongycastle:pg' include ':extern:spongycastle:pkix' include ':extern:spongycastle:prov' include ':extern:AppMsg:library' +include ':extern:SuperToasts:supertoasts' include ':extern:dnsjava' |