diff --git a/.gitmodules b/.gitmodules index a4416730d..4eab2e4eb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,7 +30,7 @@ url = https://github.com/open-keychain/SuperToasts.git [submodule "extern/KeybaseLib"] path = extern/KeybaseLib - url = https://github.com/timbray/KeybaseLib.git + url = https://github.com/open-keychain/KeybaseLib.git [submodule "extern/minidns"] path = extern/minidns url = https://github.com/open-keychain/minidns.git diff --git a/.travis.yml b/.travis.yml index 36e8f8fcf..5da831e61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,13 +4,14 @@ before_install: # Install base Android SDK - sudo apt-get update -qq - if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm lib32z1 lib32stdc++6; fi - - wget http://dl.google.com/android/android-sdk_r22.6.2-linux.tgz - - tar xzf android-sdk_r22.6.2-linux.tgz + - wget http://dl.google.com/android/android-sdk_r23-linux.tgz + - tar xzf android-sdk_r23-linux.tgz - export ANDROID_HOME=$PWD/android-sdk-linux - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools # Install required Android components. - - echo "y" | android update sdk -a --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force + #- echo "y" | android update sdk -a --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force + - ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk --no-ui --all --force --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository install: echo "Installation done" script: gradle assemble -S -q diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index c6e528f4d..3ce200008 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -59,6 +59,7 @@ + + getMailAccounts(Context context) { + public static List getPossibleUserEmails(Context context) { + Set accountMails = getAccountEmails(context); + accountMails.addAll(getMainProfileContactEmails(context)); + // now return the Set (without duplicates) as a List + return new ArrayList(accountMails); + } + + public static List getPossibleUserNames(Context context) { + Set accountMails = getAccountEmails(context); + Set names = getContactNamesFromEmails(context, accountMails); + names.addAll(getMainProfileContactName(context)); + return new ArrayList(names); + } + + /** + * Get emails from AccountManager + * + * @param context + * @return + */ + private static Set getAccountEmails(Context context) { final Account[] accounts = AccountManager.get(context).getAccounts(); final Set emailSet = new HashSet(); for (Account account : accounts) { @@ -66,7 +88,118 @@ public class ContactHelper { emailSet.add(account.name); } } - return new ArrayList(emailSet); + return emailSet; + } + + /** + * Search for contact names based on a list of emails (to find out the names of the + * device owner based on the email addresses from AccountsManager) + * + * @param context + * @param emails + * @return + */ + private static Set getContactNamesFromEmails(Context context, Set emails) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + Set names = new HashSet(); + for (String email : emails) { + ContentResolver resolver = context.getContentResolver(); + Cursor profileCursor = resolver.query( + ContactsContract.CommonDataKinds.Email.CONTENT_URI, + new String[]{ContactsContract.CommonDataKinds.Email.ADDRESS, + ContactsContract.Contacts.DISPLAY_NAME}, + ContactsContract.CommonDataKinds.Email.ADDRESS + "=?", + new String[]{email}, null + ); + if (profileCursor == null) return null; + + Set currNames = new HashSet(); + while (profileCursor.moveToNext()) { + String name = profileCursor.getString(1); + Log.d(Constants.TAG, "name" + name); + if (name != null) { + currNames.add(name); + } + } + profileCursor.close(); + names.addAll(currNames); + } + return names; + } else { + return new HashSet(); + } + } + + /** + * Retrieves the emails of the primary profile contact + * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html + * + * @param context + * @return + */ + private static Set getMainProfileContactEmails(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + ContentResolver resolver = context.getContentResolver(); + Cursor profileCursor = resolver.query( + Uri.withAppendedPath( + ContactsContract.Profile.CONTENT_URI, + ContactsContract.Contacts.Data.CONTENT_DIRECTORY), + new String[]{ContactsContract.CommonDataKinds.Email.ADDRESS, + ContactsContract.CommonDataKinds.Email.IS_PRIMARY}, + + // Selects only email addresses + ContactsContract.Contacts.Data.MIMETYPE + "=?", + new String[]{ + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE, + }, + // Show primary rows first. Note that there won't be a primary email address if the + // user hasn't specified one. + ContactsContract.Contacts.Data.IS_PRIMARY + " DESC" + ); + if (profileCursor == null) return null; + + Set emails = new HashSet(); + while (profileCursor.moveToNext()) { + String email = profileCursor.getString(0); + if (email != null) { + emails.add(email); + } + } + profileCursor.close(); + return emails; + } else { + return new HashSet(); + } + } + + /** + * Retrieves the name of the primary profile contact + * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html + * + * @param context + * @return + */ + private static List getMainProfileContactName(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + ContentResolver resolver = context.getContentResolver(); + Cursor profileCursor = resolver.query(ContactsContract.Profile.CONTENT_URI, + new String[]{ContactsContract.Profile.DISPLAY_NAME}, + null, null, null); + if (profileCursor == null) return null; + + Set names = new HashSet(); + // should only contain one entry! + while (profileCursor.moveToNext()) { + String name = profileCursor.getString(0); + if (name != null) { + names.add(name); + } + } + profileCursor.close(); + return new ArrayList(names); + } else { + return new ArrayList(); + } } public static List getContactMails(Context context) { 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 c50d92f7a..6fc55cfb8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -123,11 +123,16 @@ public class PgpImportExport { } /** Imports keys from given data. If keyIds is given only those are imported */ - public ImportResult importKeyRings(List entries) - throws PgpGeneralException, PGPException, IOException { + public ImportResult importKeyRings(List entries) { updateProgress(R.string.progress_importing, 0, 100); + // If there aren't even any keys, do nothing here. + if (entries == null || entries.size() == 0) { + return new ImportResult( + ImportResult.RESULT_FAIL_NOTHING, mProviderHelper.getLog(), 0, 0, 0); + } + int newKeys = 0, oldKeys = 0, badKeys = 0; int position = 0; @@ -165,6 +170,9 @@ public class PgpImportExport { newKeys += 1; } + } catch (IOException e) { + Log.e(Constants.TAG, "Encountered bad key on import!", e); + ++badKeys; } catch (PgpGeneralException e) { Log.e(Constants.TAG, "Encountered bad key on import!", e); ++badKeys; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 9b3e5bc54..1b59e7cc0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -33,7 +33,6 @@ import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureGenerator; import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.spongycastle.openpgp.PGPSignatureSubpacketVector; import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; @@ -51,7 +50,6 @@ import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; -import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Primes; import java.io.IOException; @@ -65,7 +63,6 @@ import java.security.SignatureException; import java.util.Arrays; import java.util.Calendar; import java.util.Date; -import java.util.List; import java.util.TimeZone; /** @@ -268,18 +265,18 @@ public class PgpKeyOperation { // 4a. For each subkey change, generate new subkey binding certificate for (SaveKeyringParcel.SubkeyChange change : saveParcel.changeSubKeys) { log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE, - new String[]{PgpKeyHelper.convertKeyIdToHex(change.mKeyId)}, indent); + indent, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId); if (sKey == null) { log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING, - new String[]{PgpKeyHelper.convertKeyIdToHex(change.mKeyId)}, indent + 1); + indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); return null; } PGPPublicKey pKey = sKey.getPublicKey(); if (change.mExpiry != null && new Date(change.mExpiry).before(new Date())) { log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, - new String[]{PgpKeyHelper.convertKeyIdToHex(change.mKeyId)}, indent + 1); + indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); return null; } @@ -293,11 +290,11 @@ public class PgpKeyOperation { // 4b. For each subkey revocation, generate new subkey revocation certificate for (long revocation : saveParcel.revokeSubKeys) { log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_REVOKE, - new String[] { PgpKeyHelper.convertKeyIdToHex(revocation) }, indent); + indent, PgpKeyHelper.convertKeyIdToHex(revocation)); PGPSecretKey sKey = sKR.getSecretKey(revocation); if (sKey == null) { log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING, - new String[] { PgpKeyHelper.convertKeyIdToHex(revocation) }, indent+1); + indent+1, PgpKeyHelper.convertKeyIdToHex(revocation)); return null; } PGPPublicKey pKey = sKey.getPublicKey(); @@ -321,7 +318,7 @@ public class PgpKeyOperation { log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent); PGPSecretKey sKey = createKey(add.mAlgorithm, add.mKeysize, passphrase, false); log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID, - new String[] { PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()) }, indent+1); + indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID())); PGPPublicKey pKey = sKey.getPublicKey(); PGPSignature cert = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey, 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 371202217..441e2762a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -207,7 +207,7 @@ public class UncachedKeyRing { 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, PgpKeyHelper.convertKeyIdToHex(getMasterKeyId())); indent += 1; final Date now = new Date(); @@ -220,7 +220,7 @@ public class UncachedKeyRing { { log.add(LogLevel.DEBUG, LogType.MSG_KC_MASTER, - new String[]{PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())}, indent); + indent, PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())); indent += 1; PGPPublicKey modified = masterKey; @@ -240,9 +240,7 @@ public class UncachedKeyRing { 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); + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TYPE, indent, "0x" + Integer.toString(type, 16)); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -250,7 +248,7 @@ public class UncachedKeyRing { if (cert.getCreationTime().after(now)) { // Creation date in the future? No way! - log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -258,7 +256,7 @@ public class UncachedKeyRing { if (cert.isLocal()) { // Creation date in the future? No way! - log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -267,13 +265,13 @@ public class UncachedKeyRing { try { cert.init(masterKey); if (!cert.verifySignature(masterKey)) { - log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } } catch (PgpGeneralException e) { - log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_ERR, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_ERR, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -286,12 +284,12 @@ public class UncachedKeyRing { } 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); + log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, indent); revocation = zert; } else { modified = PGPPublicKey.removeCertification(modified, zert); redundantCerts += 1; - log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, null, indent); + log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, indent); } } @@ -312,16 +310,14 @@ public class UncachedKeyRing { && 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); + indent, "0x" + Integer.toString(zert.getSignatureType(), 16)); 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); + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -329,7 +325,7 @@ public class UncachedKeyRing { if (cert.isLocal()) { // Creation date in the future? No way! - log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -340,7 +336,7 @@ public class UncachedKeyRing { // 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); + indent, PgpKeyHelper.convertKeyIdToHex(certId)); modified = PGPPublicKey.removeCertification(modified, userId, zert); badCerts += 1; } @@ -352,14 +348,14 @@ public class UncachedKeyRing { cert.init(masterKey); if (!cert.verifySignature(masterKey, userId)) { log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD, - new String[] { userId }, indent); + indent, userId); 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); + indent, userId); modified = PGPPublicKey.removeCertification(modified, userId, zert); badCerts += 1; continue; @@ -376,13 +372,13 @@ public class UncachedKeyRing { modified = PGPPublicKey.removeCertification(modified, userId, selfCert); redundantCerts += 1; log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP, - new String[] { userId }, indent); + indent, userId); selfCert = zert; } else { modified = PGPPublicKey.removeCertification(modified, userId, zert); redundantCerts += 1; log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP, - new String[] { userId }, indent); + indent, userId); } // If there is a revocation certificate, and it's older than this, drop it if (revocation != null @@ -391,7 +387,7 @@ public class UncachedKeyRing { revocation = null; redundantCerts += 1; log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD, - new String[] { userId }, indent); + indent, userId); } break; @@ -401,7 +397,7 @@ public class UncachedKeyRing { modified = PGPPublicKey.removeCertification(modified, userId, zert); redundantCerts += 1; log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD, - new String[] { userId }, indent); + indent, userId); continue; } // first revocation? remember it. @@ -412,13 +408,13 @@ public class UncachedKeyRing { modified = PGPPublicKey.removeCertification(modified, userId, revocation); redundantCerts += 1; log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP, - new String[] { userId }, indent); + indent, userId); 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); + indent, userId); } break; @@ -430,13 +426,13 @@ public class UncachedKeyRing { if (selfCert == null && revocation == null) { modified = PGPPublicKey.removeCertification(modified, userId); log.add(LogLevel.ERROR, LogType.MSG_KC_UID_REVOKE_DUP, - new String[] { userId }, indent); + indent, userId); } } // If NO user ids remain, error out! if (!modified.getUserIDs().hasNext()) { - log.add(LogLevel.ERROR, LogType.MSG_KC_FATAL_NO_UID, null, indent); + log.add(LogLevel.ERROR, LogType.MSG_KC_FATAL_NO_UID, indent); return null; } @@ -453,7 +449,7 @@ public class UncachedKeyRing { continue; } log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB, - new String[]{PgpKeyHelper.convertKeyIdToHex(key.getKeyID())}, indent); + indent, PgpKeyHelper.convertKeyIdToHex(key.getKeyID())); indent += 1; // A subkey needs exactly one subkey binding certificate, and optionally one revocation // certificate. @@ -468,29 +464,27 @@ public class UncachedKeyRing { // filter out bad key types... if (cert.getKeyId() != masterKey.getKeyID()) { - log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_KEYID, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_KEYID, 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); + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_TYPE, indent, "0x" + Integer.toString(type, 16)); 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); + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_TIME, 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); + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_LOCAL, indent); badCerts += 1; continue; } @@ -501,12 +495,12 @@ public class UncachedKeyRing { try { cert.init(masterKey); if (!cert.verifySignature(masterKey, key)) { - log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD, indent); badCerts += 1; continue; } } catch (PgpGeneralException e) { - log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_ERR, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_ERR, indent); badCerts += 1; continue; } @@ -526,19 +520,19 @@ public class UncachedKeyRing { if (subsig.verifySignature(masterKey, key)) { ok = true; } else { - log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD, indent); badCerts += 1; continue uids; } } } if (!ok) { - log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, indent); badCerts += 1; continue; } } catch (Exception e) { - log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD_ERR, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD_ERR, indent); badCerts += 1; continue; } @@ -565,12 +559,12 @@ public class UncachedKeyRing { try { cert.init(masterKey); if (!cert.verifySignature(key)) { - log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD, indent); badCerts += 1; continue; } } catch (PgpGeneralException e) { - log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD_ERR, null, indent); + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD_ERR, indent); badCerts += 1; continue; } @@ -591,7 +585,7 @@ public class UncachedKeyRing { ring = replacePublicKey(ring, modified); log.add(LogLevel.ERROR, LogType.MSG_KC_SUB_NO_CERT, - new String[]{ PgpKeyHelper.convertKeyIdToHex(key.getKeyID()) }, indent); + indent, PgpKeyHelper.convertKeyIdToHex(key.getKeyID())); indent -= 1; continue; } @@ -608,17 +602,17 @@ public class UncachedKeyRing { } if (badCerts > 0 && redundantCerts > 0) { + // multi plural would make this complex, just leaving this as is... log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_BAD_AND_RED, - new String[] { Integer.toString(badCerts), - Integer.toString(redundantCerts) }, indent); + indent, Integer.toString(badCerts), Integer.toString(redundantCerts)); } else if (badCerts > 0) { log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_BAD, - new String[] { Integer.toString(badCerts) }, indent); + indent, badCerts); } else if (redundantCerts > 0) { log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_REDUNDANT, - new String[] { Integer.toString(redundantCerts) }, indent); + indent, redundantCerts); } else { - log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS, null, indent); + log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS, indent); } return new UncachedKeyRing(ring, true); @@ -638,13 +632,13 @@ public class UncachedKeyRing { 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, PgpKeyHelper.convertKeyIdToHex(getMasterKeyId())); indent += 1; long masterKeyId = other.getMasterKeyId(); if (getMasterKeyId() != masterKeyId) { - log.add(LogLevel.ERROR, LogType.MSG_MG_HETEROGENEOUS, null, indent); + log.add(LogLevel.ERROR, LogType.MSG_MG_HETEROGENEOUS, indent); return null; } @@ -683,7 +677,7 @@ public class UncachedKeyRing { final PGPPublicKey resultKey = result.getPublicKey(key.getKeyID()); if (resultKey == null) { - log.add(LogLevel.DEBUG, LogType.MSG_MG_NEW_SUBKEY, null, indent); + log.add(LogLevel.DEBUG, LogType.MSG_MG_NEW_SUBKEY, indent); result = replacePublicKey(result, key); continue; } @@ -751,12 +745,12 @@ public class UncachedKeyRing { } log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW, - new String[] { Integer.toString(newCerts) }, indent); + indent, Integer.toString(newCerts)); return new UncachedKeyRing(result); } catch (IOException e) { - log.add(LogLevel.ERROR, LogType.MSG_MG_FATAL_ENCODE, null, indent); + log.add(LogLevel.ERROR, LogType.MSG_MG_FATAL_ENCODE, indent); return null; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index ceaa93f9b..7a63ec3d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -165,10 +165,10 @@ public class KeychainDatabase extends SQLiteOpenHelper { // make sure this is only done once, on the first instance! boolean iAmIt = false; - synchronized(apgHack) { - if(!apgHack) { + synchronized(KeychainDatabase.class) { + if(!KeychainDatabase.apgHack) { iAmIt = true; - apgHack = true; + KeychainDatabase.apgHack = true; } } // if it's us, do the import @@ -206,6 +206,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { try { db.execSQL("ALTER TABLE keys ADD COLUMN has_secret BOOLEAN"); } catch (Exception e) { + // never mind, the column probably already existed } } } @@ -261,8 +262,8 @@ public class KeychainDatabase extends SQLiteOpenHelper { cursor = db.rawQuery("SELECT key_ring_data FROM key_rings WHERE type = 1 OR EXISTS (" + " SELECT 1 FROM key_rings d2 WHERE key_rings.master_key_id = d2.master_key_id" + " AND d2.type = 1) ORDER BY type ASC", null); - Log.d(Constants.TAG, "Importing " + cursor.getCount() + " secret keyrings from apg.db..."); if (cursor != null) { + Log.d(Constants.TAG, "Importing " + cursor.getCount() + " secret keyrings from apg.db..."); for (int i = 0; i < cursor.getCount(); i++) { cursor.moveToPosition(i); byte[] data = cursor.getBlob(0); @@ -285,8 +286,8 @@ public class KeychainDatabase extends SQLiteOpenHelper { + " SELECT 1 FROM key_rings d2 WHERE key_rings.master_key_id = d2.master_key_id AND" + " d2.type = 1)) DESC, type DESC", null); // import from old database - Log.d(Constants.TAG, "Importing " + cursor.getCount() + " keyrings from apg.db..."); if (cursor != null) { + Log.d(Constants.TAG, "Importing " + cursor.getCount() + " keyrings from apg.db..."); for (int i = 0; i < cursor.getCount(); i++) { cursor.moveToPosition(i); byte[] data = cursor.getBlob(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 f4e19d21a..28495d51d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -116,12 +116,12 @@ public class ProviderHelper { public void log(LogLevel level, LogType type) { if(mLog != null) { - mLog.add(level, type, null, mIndent); + mLog.add(level, type, mIndent); } } - public void log(LogLevel level, LogType type, String[] parameters) { + public void log(LogLevel level, LogType type, Object... parameters) { if(mLog != null) { - mLog.add(level, type, parameters, mIndent); + mLog.add(level, type, mIndent, parameters); } } @@ -320,9 +320,9 @@ public class ProviderHelper { int rank = 0; for (UncachedPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { long keyId = key.getKeyId(); - log(LogLevel.DEBUG, keyId == masterKeyId ? LogType.MSG_IP_MASTER : LogType.MSG_IP_SUBKEY, new String[]{ + log(LogLevel.DEBUG, keyId == masterKeyId ? LogType.MSG_IP_MASTER : LogType.MSG_IP_SUBKEY, PgpKeyHelper.convertKeyIdToHex(keyId) - }); + ); mIndent += 1; ContentValues values = new ContentValues(); @@ -343,36 +343,36 @@ public class ProviderHelper { if (c) { if (e) { log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_CES - : LogType.MSG_IP_MASTER_FLAGS_CEX, null); + : LogType.MSG_IP_MASTER_FLAGS_CEX); } else { log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_CXS - : LogType.MSG_IP_MASTER_FLAGS_CXX, null); + : LogType.MSG_IP_MASTER_FLAGS_CXX); } } else { if (e) { log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_XES - : LogType.MSG_IP_MASTER_FLAGS_XEX, null); + : LogType.MSG_IP_MASTER_FLAGS_XEX); } else { log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_XXS - : LogType.MSG_IP_MASTER_FLAGS_XXX, null); + : LogType.MSG_IP_MASTER_FLAGS_XXX); } } } else { if (c) { if (e) { log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_CES - : LogType.MSG_IP_SUBKEY_FLAGS_CEX, null); + : LogType.MSG_IP_SUBKEY_FLAGS_CEX); } else { log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_CXS - : LogType.MSG_IP_SUBKEY_FLAGS_CXX, null); + : LogType.MSG_IP_SUBKEY_FLAGS_CXX); } } else { if (e) { log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_XES - : LogType.MSG_IP_SUBKEY_FLAGS_XEX, null); + : LogType.MSG_IP_SUBKEY_FLAGS_XEX); } else { log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_XXS - : LogType.MSG_IP_SUBKEY_FLAGS_XXX, null); + : LogType.MSG_IP_SUBKEY_FLAGS_XXX); } } } @@ -385,11 +385,11 @@ public class ProviderHelper { if (key.isExpired()) { log(LogLevel.DEBUG, keyId == masterKeyId ? LogType.MSG_IP_MASTER_EXPIRED : LogType.MSG_IP_SUBKEY_EXPIRED, - new String[]{ expiryDate.toString() }); + expiryDate.toString()); } else { log(LogLevel.DEBUG, keyId == masterKeyId ? LogType.MSG_IP_MASTER_EXPIRES : LogType.MSG_IP_SUBKEY_EXPIRES, - new String[] { expiryDate.toString() }); + expiryDate.toString()); } } @@ -405,9 +405,7 @@ public class ProviderHelper { // 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.INFO, LogType.MSG_IP_UID_CLASSIFYING, new String[]{ - Integer.toString(trustedKeys.size()) - }); + log(LogLevel.INFO, LogType.MSG_IP_UID_CLASSIFYING, trustedKeys.size()); mIndent += 1; List uids = new ArrayList(); for (String userId : new IterableIterator( @@ -418,7 +416,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, userId); mIndent += 1; // look through signatures for this specific key for (WrappedSignature cert : new IterableIterator( @@ -447,10 +445,10 @@ public class ProviderHelper { cert.init(trustedKey); if (cert.verifySignature(masterKey, userId)) { item.trustedCerts.add(cert); - log(LogLevel.INFO, LogType.MSG_IP_UID_CERT_GOOD, new String[] { + log(LogLevel.INFO, LogType.MSG_IP_UID_CERT_GOOD, PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId()), trustedKey.getPrimaryUserId() - }); + ); } else { log(LogLevel.WARN, LogType.MSG_IP_UID_CERT_BAD); } @@ -459,16 +457,13 @@ public class ProviderHelper { unknownCerts += 1; } catch (PgpGeneralException e) { - log(LogLevel.WARN, LogType.MSG_IP_UID_CERT_ERROR, new String[]{ - PgpKeyHelper.convertKeyIdToHex(cert.getKeyId()) - }); + log(LogLevel.WARN, LogType.MSG_IP_UID_CERT_ERROR, + PgpKeyHelper.convertKeyIdToHex(cert.getKeyId())); } } if (unknownCerts > 0) { - log(LogLevel.DEBUG, LogType.MSG_IP_UID_CERTS_UNKNOWN, new String[]{ - Integer.toString(unknownCerts) - }); + log(LogLevel.DEBUG, LogType.MSG_IP_UID_CERTS_UNKNOWN, unknownCerts); } mIndent -= 1; @@ -574,8 +569,7 @@ public class ProviderHelper { } long masterKeyId = keyRing.getMasterKeyId(); - log(LogLevel.START, LogType.MSG_IS, - new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) }); + log(LogLevel.START, LogType.MSG_IS, PgpKeyHelper.convertKeyIdToHex(masterKeyId)); mIndent += 1; try { @@ -625,18 +619,18 @@ public class ProviderHelper { int upd = mContentResolver.update(uri, values, Keys.KEY_ID + " = ?", new String[]{Long.toString(id)}); if (upd == 1) { - log(LogLevel.DEBUG, LogType.MSG_IS_SUBKEY_OK, new String[]{ + log(LogLevel.DEBUG, LogType.MSG_IS_SUBKEY_OK, PgpKeyHelper.convertKeyIdToHex(id) - }); + ); } else { - log(LogLevel.WARN, LogType.MSG_IS_SUBKEY_NONEXISTENT, new String[]{ + log(LogLevel.WARN, LogType.MSG_IS_SUBKEY_NONEXISTENT, PgpKeyHelper.convertKeyIdToHex(id) - }); + ); } } else { - log(LogLevel.INFO, LogType.MSG_IS_SUBKEY_STRIPPED, new String[]{ + log(LogLevel.INFO, LogType.MSG_IS_SUBKEY_STRIPPED, PgpKeyHelper.convertKeyIdToHex(id) - }); + ); } } mIndent -= 1; @@ -654,8 +648,6 @@ public class ProviderHelper { } - - @Deprecated public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) { return savePublicKeyRing(keyRing, new NullProgressable()); } @@ -669,8 +661,7 @@ public class ProviderHelper { try { long masterKeyId = publicRing.getMasterKeyId(); - log(LogLevel.START, LogType.MSG_IP, - new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) }); + log(LogLevel.START, LogType.MSG_IP, PgpKeyHelper.convertKeyIdToHex(masterKeyId)); mIndent += 1; // If there is an old keyring, merge it @@ -694,8 +685,8 @@ public class ProviderHelper { // 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); + log(LogLevel.OK, LogType.MSG_IP_SUCCESS_IDENTICAL); + return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog); } } catch (NotFoundException e) { // Not an issue, just means we are dealing with a new keyring. @@ -754,8 +745,7 @@ public class ProviderHelper { try { long masterKeyId = secretRing.getMasterKeyId(); - log(LogLevel.START, LogType.MSG_IS, - new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) }); + log(LogLevel.START, LogType.MSG_IS, PgpKeyHelper.convertKeyIdToHex(masterKeyId)); mIndent += 1; // If there is an old secret key, merge it. @@ -780,8 +770,8 @@ public class ProviderHelper { 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); + PgpKeyHelper.convertKeyIdToHex(masterKeyId) ); + return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog); } } catch (NotFoundException e) { // Not an issue, just means we are dealing with a new keyring @@ -812,7 +802,7 @@ public class ProviderHelper { } } catch (NotFoundException e) { - log(LogLevel.DEBUG, LogType.MSG_IS_PUBRING_GENERATE, null); + log(LogLevel.DEBUG, LogType.MSG_IS_PUBRING_GENERATE); publicRing = secretRing.extractPublicKeyRing(); } @@ -833,7 +823,7 @@ public class ProviderHelper { return new SaveKeyringResult(result, mLog); } catch (IOException e) { - log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC, null); + log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); } finally { mIndent -= 1; @@ -841,21 +831,6 @@ public class ProviderHelper { } - /** - * 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); - } - /** * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java index f88df5301..7f91ab490 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java @@ -59,23 +59,23 @@ public class OperationResultParcel implements Parcelable { public static class LogEntryParcel implements Parcelable { public final LogLevel mLevel; public final LogType mType; - public final String[] mParameters; + public final Object[] mParameters; public final int mIndent; - public LogEntryParcel(LogLevel level, LogType type, String[] parameters, int indent) { + public LogEntryParcel(LogLevel level, LogType type, int indent, Object... parameters) { mLevel = level; mType = type; mParameters = parameters; mIndent = indent; } - public LogEntryParcel(LogLevel level, LogType type, String[] parameters) { - this(level, type, parameters, 0); + public LogEntryParcel(LogLevel level, LogType type, Object... parameters) { + this(level, type, 0, parameters); } public LogEntryParcel(Parcel source) { mLevel = LogLevel.values()[source.readInt()]; mType = LogType.values()[source.readInt()]; - mParameters = source.createStringArray(); + mParameters = (Object[]) source.readSerializable(); mIndent = source.readInt(); } @@ -88,7 +88,7 @@ public class OperationResultParcel implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mLevel.ordinal()); dest.writeInt(mType.ordinal()); - dest.writeStringArray(mParameters); + dest.writeSerializable(mParameters); dest.writeInt(mIndent); } @@ -164,8 +164,8 @@ public class OperationResultParcel implements Parcelable { 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_CERTS_UNKNOWN (R.plurals.msg_ip_uid_certs_unknown), + MSG_IP_UID_CLASSIFYING (R.plurals.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), @@ -209,9 +209,9 @@ public class OperationResultParcel implements Parcelable { 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 (R.plurals.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_REDUNDANT (R.plurals.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), @@ -296,13 +296,13 @@ public class OperationResultParcel implements Parcelable { public static class OperationLog extends ArrayList { /// Simple convenience method - public void add(LogLevel level, LogType type, String[] parameters, int indent) { + public void add(LogLevel level, LogType type, int indent, Object... parameters) { Log.d(Constants.TAG, type.toString()); - add(new OperationResultParcel.LogEntryParcel(level, type, parameters, indent)); + add(new OperationResultParcel.LogEntryParcel(level, type, indent, parameters)); } public void add(LogLevel level, LogType type, int indent) { - add(new OperationResultParcel.LogEntryParcel(level, type, null, indent)); + add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null)); } public boolean containsWarnings() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java index 6c44b01f1..e5d06ccbb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java @@ -1,6 +1,19 @@ package org.sufficientlysecure.keychain.service; +import android.app.Activity; +import android.content.Intent; import android.os.Parcel; +import android.os.Parcelable; +import android.view.View; + +import com.github.johnpersano.supertoasts.SuperCardToast; +import com.github.johnpersano.supertoasts.SuperToast; +import com.github.johnpersano.supertoasts.util.OnClickWrapper; +import com.github.johnpersano.supertoasts.util.Style; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.LogDisplayActivity; +import org.sufficientlysecure.keychain.ui.LogDisplayFragment; public abstract class OperationResults { @@ -67,6 +80,86 @@ public abstract class OperationResults { } }; + public void displayToast(final Activity activity) { + + int resultType = 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 = activity.getResources().getString(R.string.import_with_warnings); + } else { + duration = SuperToast.Duration.LONG; + color = Style.GREEN; + withWarnings = ""; + } + + // New and updated keys + if (this.isOkBoth()) { + str = activity.getResources().getQuantityString( + R.plurals.import_keys_added_and_updated_1, mNewKeys, mNewKeys); + str += activity.getResources().getQuantityString( + R.plurals.import_keys_added_and_updated_2, mUpdatedKeys, mUpdatedKeys, withWarnings); + } else if (isOkUpdated()) { + str = activity.getResources().getQuantityString( + R.plurals.import_keys_updated, mUpdatedKeys, mUpdatedKeys, withWarnings); + } else if (isOkNew()) { + str = activity.getResources().getQuantityString( + R.plurals.import_keys_added, mNewKeys, mNewKeys, withWarnings); + } else { + duration = 0; + color = Style.RED; + str = "internal error"; + } + + } else { + duration = 0; + color = Style.RED; + if (isFailNothing()) { + str = activity.getString(R.string.import_error_nothing); + } else { + str = activity.getString(R.string.import_error); + } + } + + boolean button = getLog() != null && !getLog().isEmpty(); + SuperCardToast toast = new SuperCardToast(activity, + button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD, + Style.getStyle(color, SuperToast.Animations.POPUP)); + toast.setText(str); + toast.setDuration(duration); + toast.setIndeterminate(duration == 0); + toast.setSwipeToDismiss(true); + // If we have a log and it's non-empty, show a View Log button + if (button) { + toast.setButtonIcon(R.drawable.ic_action_view_as_list, + activity.getResources().getString(R.string.import_view_log)); + toast.setButtonTextColor(activity.getResources().getColor(R.color.black)); + toast.setTextColor(activity.getResources().getColor(R.color.black)); + toast.setOnClickWrapper(new OnClickWrapper("supercardtoast", + new SuperToast.OnClickListener() { + @Override + public void onClick(View view, Parcelable token) { + Intent intent = new Intent( + activity, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ImportResult.this); + activity.startActivity(intent); + } + } + )); + } + toast.show(); + + } + } public static class SaveKeyringResult extends OperationResultParcel { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityNew.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityNew.java index f2b5e68eb..b45e8a6bb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityNew.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityNew.java @@ -20,19 +20,13 @@ package org.sufficientlysecure.keychain.ui; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; -import android.view.View; -import android.view.View.OnClickListener; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ActionBarHelper; -import org.sufficientlysecure.keychain.remote.ui.AccountsListFragment; import org.sufficientlysecure.keychain.util.Log; public class EditKeyActivityNew extends ActionBarActivity { - private Uri mDataUri; - private EditKeyFragment mEditKeyFragment; @Override @@ -41,25 +35,6 @@ public class EditKeyActivityNew extends ActionBarActivity { setContentView(R.layout.edit_key_activity_new); -// // Inflate a "Done"/"Cancel" custom action bar view -// ActionBarHelper.setTwoButtonView(getSupportActionBar(), -// R.string.btn_save, R.drawable.ic_action_save, -// new OnClickListener() { -// @Override -// public void onClick(View v) { -// // Save -// -// } -// }, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel, -// new OnClickListener() { -// @Override -// public void onClick(View v) { -// // Cancel -// -// } -// } -// ); - Uri dataUri = getIntent().getData(); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index ac4390eac..3f330ee94 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -27,6 +27,7 @@ import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v7.app.ActionBarActivity; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -45,6 +46,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; +import org.sufficientlysecure.keychain.ui.adapter.UserIdsArrayAdapter; import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; @@ -68,6 +70,7 @@ public class EditKeyFragment extends LoaderFragment implements private UserIdsAdapter mUserIdsAdapter; private SubkeysAdapter mKeysAdapter; + private UserIdsArrayAdapter mUserIdsAddedAdapter; private Uri mDataUri; @@ -180,6 +183,10 @@ public class EditKeyFragment extends LoaderFragment implements } }); + mUserIdsAddedAdapter = new UserIdsArrayAdapter(getActivity()); + mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); + mUserIdsAddedAdapter.setData(mSaveKeyringParcel.addUserIds); + mKeysAdapter = new SubkeysAdapter(getActivity(), null, 0); mKeysList.setAdapter(mKeysAdapter); @@ -321,9 +328,17 @@ public class EditKeyFragment extends LoaderFragment implements Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { - if (message.what == AddUserIdDialogFragment.MESSAGE_OK) { + switch (message.what) { + case AddUserIdDialogFragment.MESSAGE_OKAY: + Bundle data = message.getData(); + String userId = data.getString(AddUserIdDialogFragment.MESSAGE_DATA_USER_ID); + if (userId != null) { + mSaveKeyringParcel.addUserIds.add(userId); + mUserIdsAddedAdapter.setData(mSaveKeyringParcel.addUserIds); + } } + getLoaderManager().getLoader(LOADER_ID_USER_IDS).forceLoad(); } }; @@ -345,5 +360,4 @@ public class EditKeyFragment extends LoaderFragment implements // TODO } - } 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 f7455905d..0d8c8233a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; +import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; @@ -64,8 +65,12 @@ public class ImportKeysActivity extends ActionBarActivity { + "IMPORT_KEY_FROM_QR_CODE"; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEYSERVER"; + public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT = + Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN_RESULT"; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN"; + public static final String ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN = Constants.INTENT_PREFIX + + "IMPORT_KEY_FROM_FILE_AND_RETURN"; public static final String ACTION_IMPORT_KEY_FROM_KEYBASE = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEYBASE"; @@ -75,6 +80,8 @@ public class ImportKeysActivity extends ActionBarActivity { public static final String ACTION_IMPORT_KEY_FROM_NFC = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_NFC"; + public static final String EXTRA_RESULT = "result"; + // only used by ACTION_IMPORT_KEY public static final String EXTRA_KEY_BYTES = "key_bytes"; @@ -165,7 +172,8 @@ public class ImportKeysActivity extends ActionBarActivity { startListFragment(savedInstanceState, importData, null, null); } } else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action) - || ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(action)) { + || ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(action) + || ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(action)) { // only used for OpenPgpService if (extras.containsKey(EXTRA_PENDING_INTENT_DATA)) { @@ -224,7 +232,8 @@ public class ImportKeysActivity extends ActionBarActivity { ); return; } - } else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) { + } else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action) + || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(action)) { // NOTE: this only displays the appropriate fragment, no actions are taken mSwitchToTab = TAB_FILE; @@ -386,9 +395,6 @@ public class ImportKeysActivity extends ActionBarActivity { /** * Scroll ViewPager left and right - * - * @param event - * @return */ @Override public boolean onTouchEvent(MotionEvent event) { @@ -427,94 +433,34 @@ public class ImportKeysActivity extends ActionBarActivity { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { // get returned data bundle Bundle returnData = message.getData(); + if (returnData == null) { + return; + } final ImportResult result = - returnData.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"; - } - - } else { - duration = 0; - color = Style.RED; - if (result.isFailNothing()) { - str = getString(R.string.import_error_nothing); - } else { - str = getString(R.string.import_error); - } + returnData.getParcelable(KeychainIntentService.RESULT); + if (result == null) { + return; } - 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_RESULT.equals(getIntent().getAction())) { + Intent intent = new Intent(); + intent.putExtra(EXTRA_RESULT, result); + ImportKeysActivity.this.setResult(RESULT_OK, intent); + ImportKeysActivity.this.finish(); + return; } - */ - - /* if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) { - ImportKeysActivity.this.setResult(Activity.RESULT_OK, mPendingIntentData); - finish(); + ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData); + ImportKeysActivity.this.finish(); + return; } - */ + if (ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) { + ImportKeysActivity.this.setResult(RESULT_OK); + ImportKeysActivity.this.finish(); + return; + } + + result.displayToast(ImportKeysActivity.this); } } }; 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 84fd513a0..9a39b6cc3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -35,6 +35,7 @@ import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListKeybaseLoader; @@ -79,6 +80,7 @@ public class ImportKeysListFragment extends ListFragment implements public ArrayList getSelectedData() { ArrayList result = new ArrayList(); for (ImportKeysListEntry entry : getSelectedEntries()) { + Log.d(Constants.TAG, "code" + Integer.toString(entry.hashCode())); result.add(mCachedKeyData.get(entry.hashCode())); } return result; @@ -207,7 +209,9 @@ public class ImportKeysListFragment extends ListFragment implements if (getLoaderManager().getLoader(LOADER_ID_KEYBASE) != null) { getLoaderManager().destroyLoader(LOADER_ID_KEYBASE); } - setListShown(true); + if (getView() != null) { + setListShown(true); + } } private void restartLoaders() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index de16142d6..7ce7a06aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -113,10 +113,10 @@ public class KeyListActivity extends DrawerActivity { } private void createKey() { - Intent intent = new Intent(this, EditKeyActivity.class); - intent.setAction(EditKeyActivity.ACTION_CREATE_KEY); - intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true); - intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view + Intent intent = new Intent(this, WizardActivity.class); +// intent.setAction(EditKeyActivity.ACTION_CREATE_KEY); +// intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true); +// intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view startActivityForResult(intent, 0); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index e42d9d00b..67317de6e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -173,7 +173,14 @@ public class LogDisplayFragment extends ListFragment implements OnTouchListener ih = (ItemHolder) convertView.getTag(); } - ih.mText.setText(getResources().getString(entry.mType.getMsgId(), (Object[]) entry.mParameters)); + // special case: first parameter may be a quantity + if (entry.mParameters != null && entry.mParameters.length > 0 + && entry.mParameters[0] instanceof Integer) { + ih.mText.setText(getResources().getQuantityString(entry.mType.getMsgId(), + (Integer) entry.mParameters[0], entry.mParameters)); + } else { + ih.mText.setText(getResources().getString(entry.mType.getMsgId(), entry.mParameters)); + } ih.mText.setTextColor(entry.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK); convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0); switch (entry.mLevel) { 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 1912b6e7d..8cc587b10 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -55,6 +55,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.OperationResults.ImportResult; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout.TabColorizer; import org.sufficientlysecure.keychain.util.Log; @@ -331,7 +332,7 @@ public class ViewKeyActivity extends ActionBarActivity implements String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob); Intent queryIntent = new Intent(this, ImportKeysActivity.class); - queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN); + queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT); queryIntent.putExtra(ImportKeysActivity.EXTRA_FINGERPRINT, fingerprint); startActivityForResult(queryIntent, REQUEST_CODE_LOOKUP_KEY); @@ -355,7 +356,10 @@ public class ViewKeyActivity extends ActionBarActivity implements switch (requestCode) { case REQUEST_CODE_LOOKUP_KEY: { if (resultCode == Activity.RESULT_OK) { - // TODO: reload key??? move this into fragment? + ImportResult result = data.getParcelableExtra(ImportKeysActivity.EXTRA_RESULT); + if (result != null) { + result.displayToast(this); + } } break; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/WizardActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/WizardActivity.java new file mode 100644 index 000000000..20b47ed01 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/WizardActivity.java @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.ActionBarActivity; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Patterns; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RadioGroup; +import android.widget.TextView; + +import org.sufficientlysecure.htmltextview.HtmlTextView; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ContactHelper; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.regex.Matcher; + + +public class WizardActivity extends ActionBarActivity { + + private State mCurrentState; + + // values for mCurrentScreen + private enum State { + START, CREATE_KEY, IMPORT_KEY, K9 + } + + public static final int REQUEST_CODE_IMPORT = 0x00007703; + + Button mBackButton; + Button mNextButton; + StartFragment mStartFragment; + CreateKeyFragment mCreateKeyFragment; + K9Fragment mK9Fragment; + + private static final String K9_PACKAGE = "com.fsck.k9"; + // private static final String K9_MARKET_INTENT_URI_BASE = "market://details?id=%s"; +// private static final Intent K9_MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse( +// String.format(K9_MARKET_INTENT_URI_BASE, K9_PACKAGE))); + private static final Intent K9_MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/k9mail/k-9/releases/tag/4.904")); + + LinearLayout mProgressLayout; + View mProgressLine; + ProgressBar mProgressBar; + ImageView mProgressImage; + TextView mProgressText; + + /** + * Checks if text of given EditText is not empty. If it is empty an error is + * set and the EditText gets the focus. + * + * @param context + * @param editText + * @return true if EditText is not empty + */ + private static boolean isEditTextNotEmpty(Context context, EditText editText) { + boolean output = true; + if (editText.getText().toString().length() == 0) { + editText.setError("empty!"); + editText.requestFocus(); + output = false; + } else { + editText.setError(null); + } + + return output; + } + + public static class StartFragment extends Fragment { + public static StartFragment newInstance() { + StartFragment myFragment = new StartFragment(); + + Bundle args = new Bundle(); + myFragment.setArguments(args); + + return myFragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.wizard_start_fragment, + container, false); + } + } + + public static class CreateKeyFragment extends Fragment { + public static CreateKeyFragment newInstance() { + CreateKeyFragment myFragment = new CreateKeyFragment(); + + Bundle args = new Bundle(); + myFragment.setArguments(args); + + return myFragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.wizard_create_key_fragment, + container, false); + + final AutoCompleteTextView emailView = (AutoCompleteTextView) view.findViewById(R.id.email); + emailView.setThreshold(1); // Start working from first character + emailView.setAdapter( + new ArrayAdapter + (getActivity(), android.R.layout.simple_dropdown_item_1line, + ContactHelper.getPossibleUserEmails(getActivity()) + ) + ); + emailView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void afterTextChanged(Editable editable) { + String email = editable.toString(); + if (email.length() > 0) { + Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email); + if (emailMatcher.matches()) { + emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_ok, 0); + } else { + emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_bad, 0); + } + } else { + // remove drawable if email is empty + emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + } + }); + final AutoCompleteTextView nameView = (AutoCompleteTextView) view.findViewById(R.id.name); + nameView.setThreshold(1); // Start working from first character + nameView.setAdapter( + new ArrayAdapter + (getActivity(), android.R.layout.simple_dropdown_item_1line, + ContactHelper.getPossibleUserNames(getActivity()) + ) + ); + return view; + } + } + + public static class K9Fragment extends Fragment { + public static K9Fragment newInstance() { + K9Fragment myFragment = new K9Fragment(); + + Bundle args = new Bundle(); + myFragment.setArguments(args); + + return myFragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.wizard_k9_fragment, + container, false); + + HtmlTextView text = (HtmlTextView) v + .findViewById(R.id.wizard_k9_text); + text.setHtmlFromString("Install K9. It's good for you! Here is a screenhot how to enable OK in K9: (TODO)", true); + + return v; + } + + } + + /** + * Loads new fragment + * + * @param fragment + */ + private void loadFragment(Fragment fragment) { + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentTransaction fragmentTransaction = fragmentManager + .beginTransaction(); + fragmentTransaction.replace(R.id.wizard_container, + fragment); + fragmentTransaction.commit(); + } + + /** + * Instantiate View and initialize fragments for this Activity + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.wizard_activity); + mBackButton = (Button) findViewById(R.id.wizard_back); + mNextButton = (Button) findViewById(R.id.wizard_next); + + // progress layout + mProgressLayout = (LinearLayout) findViewById(R.id.wizard_progress); + mProgressLine = findViewById(R.id.wizard_progress_line); + mProgressBar = (ProgressBar) findViewById(R.id.wizard_progress_progressbar); + mProgressImage = (ImageView) findViewById(R.id.wizard_progress_image); + mProgressText = (TextView) findViewById(R.id.wizard_progress_text); + + changeToState(State.START); + } + + private enum ProgressState { + WORKING, ENABLED, DISABLED, ERROR + } + + private void showProgress(ProgressState state, String text) { + switch (state) { + case WORKING: + mProgressBar.setVisibility(View.VISIBLE); + mProgressImage.setVisibility(View.GONE); + break; + case ENABLED: + mProgressBar.setVisibility(View.GONE); + mProgressImage.setVisibility(View.VISIBLE); +// mProgressImage.setImageDrawable(getResources().getDrawable( +// R.drawable.status_enabled)); + break; + case DISABLED: + mProgressBar.setVisibility(View.GONE); + mProgressImage.setVisibility(View.VISIBLE); +// mProgressImage.setImageDrawable(getResources().getDrawable( +// R.drawable.status_disabled)); + break; + case ERROR: + mProgressBar.setVisibility(View.GONE); + mProgressImage.setVisibility(View.VISIBLE); +// mProgressImage.setImageDrawable(getResources().getDrawable( +// R.drawable.status_fail)); + break; + + default: + break; + } + mProgressText.setText(text); + + mProgressLine.setVisibility(View.VISIBLE); + mProgressLayout.setVisibility(View.VISIBLE); + } + + private void hideProgress() { + mProgressLine.setVisibility(View.GONE); + mProgressLayout.setVisibility(View.GONE); + } + + public void nextOnClick(View view) { + // close keyboard + if (getCurrentFocus() != null) { + InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.hideSoftInputFromWindow(getCurrentFocus() + .getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); + } + + switch (mCurrentState) { + case START: { + RadioGroup radioGroup = (RadioGroup) findViewById(R.id.wizard_start_radio_group); + int selectedId = radioGroup.getCheckedRadioButtonId(); + switch (selectedId) { + case R.id.wizard_start_new_key: { + changeToState(State.CREATE_KEY); + break; + } + case R.id.wizard_start_import: { + changeToState(State.IMPORT_KEY); + break; + } + case R.id.wizard_start_skip: { + finish(); + break; + } + } + + mBackButton.setText(R.string.btn_back); + break; + } + case CREATE_KEY: + EditText nameEdit = (EditText) findViewById(R.id.name); + EditText emailEdit = (EditText) findViewById(R.id.email); + EditText passphraseEdit = (EditText) findViewById(R.id.passphrase); + + if (isEditTextNotEmpty(this, nameEdit) + && isEditTextNotEmpty(this, emailEdit) + && isEditTextNotEmpty(this, passphraseEdit)) { + +// SaveKeyringParcel newKey = new SaveKeyringParcel(); +// newKey.addUserIds.add(nameEdit.getText().toString() + " <" +// + emailEdit.getText().toString() + ">"); + + + AsyncTask generateTask = new AsyncTask() { + + @Override + protected void onPreExecute() { + super.onPreExecute(); + + showProgress(ProgressState.WORKING, "generating key..."); + } + + @Override + protected Boolean doInBackground(String... params) { + return true; + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + + if (result) { + showProgress(ProgressState.ENABLED, "key generated successfully!"); + + changeToState(State.K9); + } else { + showProgress(ProgressState.ERROR, "error in key gen"); + } + } + + }; + + generateTask.execute(""); + } + break; + case K9: { + RadioGroup radioGroup = (RadioGroup) findViewById(R.id.wizard_k9_radio_group); + int selectedId = radioGroup.getCheckedRadioButtonId(); + switch (selectedId) { + case R.id.wizard_k9_install: { + try { + startActivity(K9_MARKET_INTENT); + } catch (ActivityNotFoundException e) { + Log.e(Constants.TAG, "Activity not found for: " + K9_MARKET_INTENT); + } + break; + } + case R.id.wizard_k9_skip: { + finish(); + break; + } + } + + finish(); + break; + } + default: + break; + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_CODE_IMPORT: { + if (resultCode == Activity.RESULT_OK) { + // imported now... + changeToState(State.K9); + } else { + // back to start + changeToState(State.START); + } + break; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + + break; + } + } + } + + public void backOnClick(View view) { + switch (mCurrentState) { + case START: + finish(); + break; + case CREATE_KEY: + changeToState(State.START); + break; + case IMPORT_KEY: + changeToState(State.START); + break; + default: + changeToState(State.START); + break; + } + } + + private void changeToState(State state) { + switch (state) { + case START: { + mCurrentState = State.START; + mStartFragment = StartFragment.newInstance(); + loadFragment(mStartFragment); + mBackButton.setText(android.R.string.cancel); + mNextButton.setText(R.string.btn_next); + break; + } + case CREATE_KEY: { + mCurrentState = State.CREATE_KEY; + mCreateKeyFragment = CreateKeyFragment.newInstance(); + loadFragment(mCreateKeyFragment); + break; + } + case IMPORT_KEY: { + mCurrentState = State.IMPORT_KEY; + Intent intent = new Intent(this, ImportKeysActivity.class); + intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); + startActivityForResult(intent, REQUEST_CODE_IMPORT); + break; + } + case K9: { + mCurrentState = State.K9; + mBackButton.setEnabled(false); // don't go back to import/create key + mK9Fragment = K9Fragment.newInstance(); + loadFragment(mK9Fragment); + break; + } + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index a44d32e5b..7a55f9aaa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -85,6 +85,10 @@ public class ImportKeysAdapter extends ArrayAdapter { public ArrayList getSelectedEntries() { ArrayList selectedData = new ArrayList(); + // Nothing to select, nvm. + if (mData == null) { + return selectedData; + } for (ImportKeysListEntry entry : mData) { if (entry.isSelected()) { selectedData.add(entry); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index 7a7acfe89..7fc78dc41 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -144,7 +144,6 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC vComment.setVisibility(View.GONE); } - // show small star icon for primary user ids boolean isPrimary = cursor.getInt(mIsPrimary) != 0; boolean isRevoked = cursor.getInt(mIsRevoked) > 0; @@ -185,6 +184,8 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC vName.setEnabled(true); vAddress.setEnabled(true); + // verified: has been verified + // isPrimary: show small star icon for primary user ids int verified = cursor.getInt(mVerifiedId); switch (verified) { case Certs.VERIFIED_SECRET: diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsArrayAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsArrayAdapter.java new file mode 100644 index 000000000..e6445c074 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsArrayAdapter.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2013-2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui.adapter; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; + +import java.util.List; + +public class UserIdsArrayAdapter extends ArrayAdapter { + protected LayoutInflater mInflater; + protected Activity mActivity; + + protected List mData; + + static class ViewHolder { + public TextView vName; + public TextView vAddress; + public TextView vComment; + public ImageView vVerified; + public ImageView vHasChanges; + public CheckBox vCheckBox; + } + + public UserIdsArrayAdapter(Activity activity) { + super(activity, -1); + mActivity = activity; + mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void setData(List data) { + clear(); + if (data != null) { + this.mData = data; + + // add data to extended ArrayAdapter + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + addAll(data); + } else { + for (String entry : data) { + add(entry); + } + } + } + } + + public List getData() { + return mData; + } + + @Override + public boolean hasStableIds() { + return true; + } + + public View getView(int position, View convertView, ViewGroup parent) { + String entry = mData.get(position); + ViewHolder holder; + if (convertView == null) { + holder = new ViewHolder(); + convertView = mInflater.inflate(R.layout.view_key_userids_item, null); + holder.vName = (TextView) convertView.findViewById(R.id.userId); + holder.vAddress = (TextView) convertView.findViewById(R.id.address); + holder.vComment = (TextView) convertView.findViewById(R.id.comment); + holder.vVerified = (ImageView) convertView.findViewById(R.id.certified); + holder.vHasChanges = (ImageView) convertView.findViewById(R.id.has_changes); + holder.vCheckBox = (CheckBox) convertView.findViewById(R.id.checkBox); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + + // user id + String[] splitUserId = KeyRing.splitUserId(entry); + if (splitUserId[0] != null) { + holder.vName.setText(splitUserId[0]); + } else { + holder.vName.setText(R.string.user_id_no_name); + } + if (splitUserId[1] != null) { + holder.vAddress.setText(splitUserId[1]); + holder.vAddress.setVisibility(View.VISIBLE); + } else { + holder.vAddress.setVisibility(View.GONE); + } + if (splitUserId[2] != null) { + holder.vComment.setText(splitUserId[2]); + holder.vComment.setVisibility(View.VISIBLE); + } else { + holder.vComment.setVisibility(View.GONE); + } + + holder.vCheckBox.setVisibility(View.GONE); + + holder.vVerified.setImageResource(R.drawable.key_certify_ok_depth0); + + // all items are "new" + holder.vHasChanges.setVisibility(View.VISIBLE); + + return convertView; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java index db7c38e71..c27266e3f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java @@ -24,17 +24,32 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.DialogFragment; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Log; -public class AddUserIdDialogFragment extends DialogFragment { +public class AddUserIdDialogFragment extends DialogFragment implements EditText.OnEditorActionListener { private static final String ARG_MESSENGER = "messenger"; - public static final int MESSAGE_OK = 1; + public static final int MESSAGE_OKAY = 1; + + public static final String MESSAGE_DATA_USER_ID = "user_id"; private Messenger mMessenger; + EditText mName; + EditText mAddress; + EditText mComment; + /** * Creates new instance of this dialog fragment */ @@ -55,34 +70,75 @@ public class AddUserIdDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { mMessenger = getArguments().getParcelable(ARG_MESSENGER); - CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(getActivity()); -// CharSequence[] array = {"change to primary user id", "revoke"}; -// -// builder.setTitle("select action!"); -// builder.setItems(array, new DialogInterface.OnClickListener() { -// -// @Override -// public void onClick(DialogInterface dialog, int which) { -// switch (which) { -// case 0: -// sendMessageToHandler(MESSAGE_CHANGE_PRIMARY_USER_ID, null); -// break; -// case 1: -// sendMessageToHandler(MESSAGE_REVOKE, null); -// break; -// default: -// break; -// } -// } -// }); -// builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() { -// @Override -// public void onClick(DialogInterface dialog, int id) { -// dismiss(); -// } -// }); + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(getActivity()); + LayoutInflater inflater = getActivity().getLayoutInflater(); + View view = inflater.inflate(R.layout.add_user_id_dialog, null); + alert.setView(view); + alert.setTitle("Add Identity"); - return builder.show(); + mName = (EditText) view.findViewById(R.id.name); + mAddress = (EditText) view.findViewById(R.id.address); + mComment = (EditText) view.findViewById(R.id.comment); + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + done(); + } + }); + + alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + + return alert.show(); + } + + @Override + public void onActivityCreated(Bundle arg0) { + super.onActivityCreated(arg0); + // Show soft keyboard automatically + mName.requestFocus(); + getDialog().getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + mComment.setOnEditorActionListener(this); + } + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (EditorInfo.IME_ACTION_DONE == actionId) { + done(); + return true; + } + return false; + } + + private void done() { + String name = mName.getText().toString(); + String email = mAddress.getText().toString(); + String comment = mComment.getText().toString(); + + String userId = null; + if (!TextUtils.isEmpty(name)) { + userId = name; + if (!TextUtils.isEmpty(comment)) { + userId += " (" + comment + ")"; + } + if (!TextUtils.isEmpty(email)) { + userId += " <" + email + ">"; + } + } + Bundle data = new Bundle(); + data.putString(MESSAGE_DATA_USER_ID, userId); + sendMessageToHandler(MESSAGE_OKAY, data); + + this.dismiss(); } /** @@ -105,4 +161,5 @@ public class AddUserIdDialogFragment extends DialogFragment { Log.w(Constants.TAG, "Messenger is null!", e); } } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java index bc1c84271..9b561f819 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java @@ -118,7 +118,7 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene mEmail.setAdapter( new ArrayAdapter (this.getContext(), android.R.layout.simple_dropdown_item_1line, - ContactHelper.getMailAccounts(getContext()) + ContactHelper.getPossibleUserEmails(getContext()) )); mEmail.addTextChangedListener(new TextWatcher(){ @Override diff --git a/OpenKeychain/src/main/res/layout/add_user_id_dialog.xml b/OpenKeychain/src/main/res/layout/add_user_id_dialog.xml new file mode 100644 index 000000000..502ca1c70 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/add_user_id_dialog.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/edit_key_fragment.xml b/OpenKeychain/src/main/res/layout/edit_key_fragment.xml index f652269e5..7f94cb3cd 100644 --- a/OpenKeychain/src/main/res/layout/edit_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/edit_key_fragment.xml @@ -45,6 +45,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> + + + + + + + + + + +