diff options
Diffstat (limited to 'OpenKeychain')
67 files changed, 2679 insertions, 1023 deletions
| 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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> | 
