diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org')
159 files changed, 8409 insertions, 4849 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 04a932658..67fa30a44 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -20,11 +20,6 @@ package org.sufficientlysecure.keychain; import android.os.Environment; import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.sufficientlysecure.keychain.remote.ui.AppsListActivity; -import org.sufficientlysecure.keychain.ui.DecryptActivity; -import org.sufficientlysecure.keychain.ui.EncryptFilesActivity; -import org.sufficientlysecure.keychain.ui.EncryptTextActivity; -import org.sufficientlysecure.keychain.ui.KeyListActivity; import java.io.File; @@ -91,21 +86,6 @@ public final class Constants { public static final int PREF_VERSION = 4; } - public static final class DrawerItems { - public static final Class KEY_LIST = KeyListActivity.class; - public static final Class ENCRYPT_TEXT = EncryptTextActivity.class; - public static final Class ENCRYPT_FILE = EncryptFilesActivity.class; - public static final Class DECRYPT = DecryptActivity.class; - public static final Class REGISTERED_APPS_LIST = AppsListActivity.class; - public static final Class[] ARRAY = new Class[]{ - KEY_LIST, - ENCRYPT_TEXT, - ENCRYPT_FILE, - DECRYPT, - REGISTERED_APPS_LIST - }; - } - public static final class key { public static final int none = 0; public static final int symmetric = -1; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index 446699a81..01f6735ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -32,12 +32,12 @@ import android.provider.ContactsContract; import android.widget.Toast; import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.TlsHelper; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PRNGFixes; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.TlsHelper; import java.security.Security; @@ -86,7 +86,7 @@ public class KeychainApplication extends Application { } brandGlowEffect(getApplicationContext(), - getApplicationContext().getResources().getColor(R.color.emphasis)); + getApplicationContext().getResources().getColor(R.color.primary)); setupAccountAsNeeded(this); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java index 255ca1cde..c0221fad3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java @@ -17,8 +17,8 @@ package org.sufficientlysecure.keychain.keyimport; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; import java.util.Vector; @@ -32,10 +32,10 @@ public class CloudSearch { public static ArrayList<ImportKeysListEntry> search(final String query, Preferences.CloudSearchPrefs cloudPrefs) throws Keyserver.CloudSearchFailureException { - final ArrayList<Keyserver> servers = new ArrayList<Keyserver>(); + final ArrayList<Keyserver> servers = new ArrayList<>(); // it's a Vector for sync, multiple threads might report problems - final Vector<Keyserver.CloudSearchFailureException> problems = new Vector<Keyserver.CloudSearchFailureException>(); + final Vector<Keyserver.CloudSearchFailureException> problems = new Vector<>(); if (cloudPrefs.searchKeyserver) { servers.add(new HkpKeyserver(cloudPrefs.keyserver)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index 7f07b8162..480319081 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -19,10 +19,10 @@ package org.sufficientlysecure.keychain.keyimport; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.TlsHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.TlsHelper; import java.io.BufferedWriter; import java.io.IOException; @@ -234,7 +234,7 @@ public class HkpKeyserver extends Keyserver { @Override public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException, QueryNeedsRepairException { - ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>(); + ArrayList<ImportKeysListEntry> results = new ArrayList<>(); if (query.length() < 3) { throw new QueryTooShortException(); @@ -305,7 +305,7 @@ public class HkpKeyserver extends Keyserver { entry.setRevoked(matcher.group(6).contains("r")); entry.setExpired(matcher.group(6).contains("e")); - ArrayList<String> userIds = new ArrayList<String>(); + ArrayList<String> userIds = new ArrayList<>(); final String uidLines = matcher.group(7); final Matcher uidMatcher = UID_LINE.matcher(uidLines); while (uidMatcher.find()) { 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 854a90713..79065604a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -21,10 +21,11 @@ import android.content.Context; import android.os.Parcel; import android.os.Parcelable; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import java.io.Serializable; import java.util.ArrayList; @@ -88,7 +89,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { public ImportKeysListEntry createFromParcel(final Parcel source) { ImportKeysListEntry vr = new ImportKeysListEntry(); vr.mPrimaryUserId = source.readString(); - vr.mUserIds = new ArrayList<String>(); + vr.mUserIds = new ArrayList<>(); source.readStringList(vr.mUserIds); vr.mMergedUserIds = (HashMap<String, HashSet<String>>) source.readSerializable(); vr.mKeyId = source.readLong(); @@ -102,7 +103,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { vr.mSecretKey = source.readByte() == 1; vr.mSelected = source.readByte() == 1; vr.mExtraData = source.readString(); - vr.mOrigins = new ArrayList<String>(); + vr.mOrigins = new ArrayList<>(); source.readStringList(vr.mOrigins); return vr; @@ -264,8 +265,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mSecretKey = false; // do not select by default mSelected = false; - mUserIds = new ArrayList<String>(); - mOrigins = new ArrayList<String>(); + mUserIds = new ArrayList<>(); + mOrigins = new ArrayList<>(); } /** @@ -287,14 +288,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable { // if there was no user id flagged as primary, use the first one if (mPrimaryUserId == null) { - mPrimaryUserId = mUserIds.get(0); + mPrimaryUserId = context.getString(R.string.user_id_none); } mKeyId = key.getKeyId(); mKeyIdHex = KeyFormattingUtils.convertKeyIdToHex(mKeyId); - mRevoked = key.isRevoked(); - mExpired = key.isExpired(); + mRevoked = key.isMaybeRevoked(); + mExpired = key.isMaybeExpired(); mFingerprintHex = KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint()); mBitStrength = key.getBitStrength(); mCurveOid = key.getCurveOid(); @@ -303,7 +304,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { } public void updateMergedUserIds() { - mMergedUserIds = new HashMap<String, HashSet<String>>(); + mMergedUserIds = new HashMap<>(); for (String userId : mUserIds) { String[] userIdSplit = KeyRing.splitUserId(userId); @@ -314,7 +315,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { // email if (userIdSplit[1] != null) { if (!mMergedUserIds.containsKey(userIdSplit[0])) { - HashSet<String> emails = new HashSet<String>(); + HashSet<String> emails = new HashSet<>(); emails.add(userIdSplit[1]); mMergedUserIds.put(userIdSplit[0], emails); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java index 2363a40b3..e310e9a3f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -36,7 +36,7 @@ public class KeybaseKeyserver extends Keyserver { @Override public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException, QueryNeedsRepairException { - ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>(); + ArrayList<ImportKeysListEntry> results = new ArrayList<>(); if (query.startsWith("0x")) { // cut off "0x" if a user is searching for a key id @@ -81,7 +81,7 @@ public class KeybaseKeyserver extends Keyserver { final int algorithmId = match.getAlgorithmId(); entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitStrength, null)); - ArrayList<String> userIds = new ArrayList<String>(); + ArrayList<String> userIds = new ArrayList<>(); String name = "<keybase.io/" + username + ">"; if (fullName != null) { name = fullName + " " + name; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java index c400eb813..e796bdc91 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java @@ -77,6 +77,12 @@ public abstract class BaseOperation implements PassphraseCacheInterface { return mCancelled != null && mCancelled.get(); } + protected void setPreventCancel () { + if (mProgressable != null) { + mProgressable.setPreventCancel(); + } + } + @Override public String getCachedPassphrase(long subKeyId) throws NoSecretKeyException { try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 3bd412c36..025f45f7f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -5,6 +5,10 @@ import android.content.Context; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; +import org.sufficientlysecure.keychain.operations.results.CertifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; @@ -16,10 +20,6 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; -import org.sufficientlysecure.keychain.operations.results.CertifyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -50,9 +50,6 @@ public class CertifyOperation extends BaseOperation { CanonicalizedSecretKey certificationKey; try { - // certification is always with the master key id, so use that one - String passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId); - log.add(LogType.MSG_CRT_MASTER_FETCH, 1); CanonicalizedSecretKeyRing secretKeyRing = mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId); @@ -62,6 +59,10 @@ public class CertifyOperation extends BaseOperation { log.add(LogType.MSG_CRT_ERROR_DIVERT, 2); return new CertifyResult(CertifyResult.RESULT_ERROR, log); } + + // certification is always with the master key id, so use that one + String passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId); + if (!certificationKey.unlock(passphrase)) { log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2); return new CertifyResult(CertifyResult.RESULT_ERROR, log); @@ -77,7 +78,7 @@ public class CertifyOperation extends BaseOperation { return new CertifyResult(CertifyResult.RESULT_ERROR, log); } - ArrayList<UncachedKeyRing> certifiedKeys = new ArrayList<UncachedKeyRing>(); + ArrayList<UncachedKeyRing> certifiedKeys = new ArrayList<>(); log.add(LogType.MSG_CRT_CERTIFYING, 1); @@ -94,6 +95,12 @@ public class CertifyOperation extends BaseOperation { try { + if (action.mMasterKeyId == parcel.mMasterKeyId) { + log.add(LogType.MSG_CRT_ERROR_SELF, 2); + certifyError += 1; + continue; + } + if (action.mUserIds == null) { log.add(LogType.MSG_CRT_CERTIFY_ALL, 2, KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java new file mode 100644 index 000000000..7f14b08d9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -0,0 +1,131 @@ +package org.sufficientlysecure.keychain.operations; + +import android.content.Context; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; +import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ProgressScaler; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** An operation which implements a high level key edit operation. + * + * This operation provides a higher level interface to the edit and + * create key operations in PgpKeyOperation. It takes care of fetching + * and saving the key before and after the operation. + * + * @see SaveKeyringParcel + * + */ +public class EditKeyOperation extends BaseOperation { + + public EditKeyOperation(Context context, ProviderHelper providerHelper, + Progressable progressable, AtomicBoolean cancelled) { + super(context, providerHelper, progressable, cancelled); + } + + public EditKeyResult execute(SaveKeyringParcel saveParcel, String passphrase) { + + OperationLog log = new OperationLog(); + log.add(LogType.MSG_ED, 0); + + if (saveParcel == null) { + log.add(LogType.MSG_ED_ERROR_NO_PARCEL, 1); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + // Perform actual modification (or creation) + PgpEditKeyResult modifyResult; + { + PgpKeyOperation keyOperations = + new PgpKeyOperation(new ProgressScaler(mProgressable, 10, 60, 100), mCancelled); + + // If a key id is specified, fetch and edit + if (saveParcel.mMasterKeyId != null) { + try { + + log.add(LogType.MSG_ED_FETCHING, 1, + KeyFormattingUtils.convertKeyIdToHex(saveParcel.mMasterKeyId)); + CanonicalizedSecretKeyRing secRing = + mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId); + + modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase); + + } catch (NotFoundException e) { + log.add(LogType.MSG_ED_ERROR_KEY_NOT_FOUND, 2); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + } else { + // otherwise, create new one + modifyResult = keyOperations.createSecretKeyRing(saveParcel); + } + } + + // Add the result to the log + log.add(modifyResult, 1); + + // Check if the action was cancelled + if (checkCancelled()) { + log.add(LogType.MSG_OPERATION_CANCELLED, 0); + return new EditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); + } + + // If the edit operation didn't succeed, exit here + if (!modifyResult.success()) { + // error is already logged by modification + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + // Cannot cancel from here on out! + mProgressable.setPreventCancel(); + + // It's a success, so this must be non-null now + UncachedKeyRing ring = modifyResult.getRing(); + + // Save the new keyring. + SaveKeyringResult saveResult = mProviderHelper + .saveSecretKeyRing(ring, new ProgressScaler(mProgressable, 60, 95, 100)); + log.add(saveResult, 1); + + // If the save operation didn't succeed, exit here + if (!saveResult.success()) { + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + // There is a new passphrase - cache it + if (saveParcel.mNewUnlock != null) { + log.add(LogType.MSG_ED_CACHING_NEW, 1); + PassphraseCacheService.addCachedPassphrase(mContext, + ring.getMasterKeyId(), + ring.getMasterKeyId(), + saveParcel.mNewUnlock.mNewPassphrase != null + ? saveParcel.mNewUnlock.mNewPassphrase + : saveParcel.mNewUnlock.mNewPin, + ring.getPublicKey().getPrimaryUserIdWithFallback()); + } + + updateProgress(R.string.progress_done, 100, 100); + + // make sure new data is synced into contacts + ContactSyncAdapterService.requestSync(); + + log.add(LogType.MSG_ED_SUCCESS, 0); + return new EditKeyResult(EditKeyResult.RESULT_OK, log, ring.getMasterKeyId()); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java index da532d2dc..8f10377cd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java @@ -30,7 +30,13 @@ import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.operations.results.ExportResult; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.Progressable; @@ -39,13 +45,12 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableFileCache; +import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.BufferedOutputStream; @@ -57,6 +62,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** An operation class which implements high level import and export @@ -122,6 +128,35 @@ public class ImportExportOperation extends BaseOperation { } } + public ImportKeyResult importKeyRings(List<ParcelableKeyRing> entries, String keyServerUri) { + + Iterator<ParcelableKeyRing> it = entries.iterator(); + int numEntries = entries.size(); + + return importKeyRings(it, numEntries, keyServerUri); + + } + + public ImportKeyResult importKeyRings(ParcelableFileCache<ParcelableKeyRing> cache, String keyServerUri) { + + // get entries from cached file + try { + IteratorWithSize<ParcelableKeyRing> it = cache.readCache(); + int numEntries = it.getSize(); + + return importKeyRings(it, numEntries, keyServerUri); + } catch (IOException e) { + + // Special treatment here, we need a lot + OperationLog log = new OperationLog(); + log.add(LogType.MSG_IMPORT, 0, 0); + log.add(LogType.MSG_IMPORT_ERROR_IO, 0, 0); + + return new ImportKeyResult(ImportKeyResult.RESULT_ERROR, log); + } + + } + public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num, String keyServerUri) { updateProgress(R.string.progress_importing, 0, 100); @@ -130,13 +165,11 @@ public class ImportExportOperation extends BaseOperation { // If there aren't even any keys, do nothing here. if (entries == null || !entries.hasNext()) { - return new ImportKeyResult( - ImportKeyResult.RESULT_FAIL_NOTHING, log, 0, 0, 0, 0, - new long[]{}); + return new ImportKeyResult(ImportKeyResult.RESULT_FAIL_NOTHING, log); } int newKeys = 0, oldKeys = 0, badKeys = 0, secret = 0; - ArrayList<Long> importedMasterKeyIds = new ArrayList<Long>(); + ArrayList<Long> importedMasterKeyIds = new ArrayList<>(); boolean cancelled = false; int position = 0; @@ -292,6 +325,16 @@ public class ImportExportOperation extends BaseOperation { position++; } + // Special: consolidate on secret key import (cannot be cancelled!) + if (secret > 0) { + setPreventCancel(); + ConsolidateResult result = mProviderHelper.consolidateDatabaseStep1(mProgressable); + log.add(result, 1); + } + + // Special: make sure new data is synced into contacts + ContactSyncAdapterService.requestSync(); + // convert to long array long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()]; for (int i = 0; i < importedMasterKeyIds.size(); ++i) { @@ -399,7 +442,7 @@ public class ImportExportOperation extends BaseOperation { } - private ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret, + ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret, OutputStream outStream) { /* TODO isn't this checked above, with the isStorageMounted call? @@ -469,12 +512,16 @@ public class ImportExportOperation extends BaseOperation { log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId)); - { // export public key part - byte[] data = cursor.getBlob(1); - arOutStream.write(data); + byte[] data = cursor.getBlob(1); + CanonicalizedKeyRing ring = + UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true); + ring.encode(arOutStream); - okPublic += 1; - } + okPublic += 1; + } catch (PgpGeneralException e) { + log.add(LogType.MSG_EXPORT_ERROR_KEY, 2); + updateProgress(progress++, numKeys); + continue; } finally { // make sure this is closed if (arOutStream != null) { @@ -491,12 +538,18 @@ public class ImportExportOperation extends BaseOperation { arOutStream.setHeader("Version", version); } - // export secret key part + // export secret key part log.add(LogType.MSG_EXPORT_SECRET, 2, KeyFormattingUtils.beautifyKeyId(keyId)); byte[] data = cursor.getBlob(2); - arOutStream.write(data); + CanonicalizedKeyRing ring = + UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true); + ring.encode(arOutStream); okSecret += 1; + } catch (PgpGeneralException e) { + log.add(LogType.MSG_EXPORT_ERROR_KEY, 2); + updateProgress(progress++, numKeys); + continue; } finally { // make sure this is closed if (arOutStream != null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java new file mode 100644 index 000000000..783b32477 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java @@ -0,0 +1,103 @@ +package org.sufficientlysecure.keychain.operations; + +import android.content.Context; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; +import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ProgressScaler; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** An operation which promotes a public key ring to a secret one. + * + * This operation can only be applied to public key rings where no secret key + * is available. Doing this "promotes" the public key ring to a secret one + * without secret key material, using a GNU_DUMMY s2k type. + * + */ +public class PromoteKeyOperation extends BaseOperation { + + public PromoteKeyOperation(Context context, ProviderHelper providerHelper, + Progressable progressable, AtomicBoolean cancelled) { + super(context, providerHelper, progressable, cancelled); + } + + public PromoteKeyResult execute(long masterKeyId) { + + OperationLog log = new OperationLog(); + log.add(LogType.MSG_PR, 0); + + // Perform actual type change + UncachedKeyRing promotedRing; + { + + try { + + // This operation is only allowed for pure public keys + // TODO delete secret keys if they are stripped, or have been moved to the card? + if (mProviderHelper.getCachedPublicKeyRing(masterKeyId).hasAnySecret()) { + log.add(LogType.MSG_PR_ERROR_ALREADY_SECRET, 2); + return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null); + } + + log.add(LogType.MSG_PR_FETCHING, 1, + KeyFormattingUtils.convertKeyIdToHex(masterKeyId)); + CanonicalizedPublicKeyRing pubRing = + mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId); + + // create divert-to-card secret key from public key + promotedRing = pubRing.createDummySecretRing(); + + } catch (PgpKeyNotFoundException e) { + log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2); + return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null); + } catch (NotFoundException e) { + log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2); + return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null); + } + } + + // If the edit operation didn't succeed, exit here + if (promotedRing == null) { + // error is already logged by modification + return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null); + } + + // Check if the action was cancelled + if (checkCancelled()) { + log.add(LogType.MSG_OPERATION_CANCELLED, 0); + return new PromoteKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); + } + + // Cannot cancel from here on out! + setPreventCancel(); + + // Save the new keyring. + SaveKeyringResult saveResult = mProviderHelper + .saveSecretKeyRing(promotedRing, new ProgressScaler(mProgressable, 60, 95, 100)); + log.add(saveResult, 1); + + // If the save operation didn't succeed, exit here + if (!saveResult.success()) { + return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null); + } + + updateProgress(R.string.progress_done, 100, 100); + + log.add(LogType.MSG_PR_SUCCESS, 0); + return new PromoteKeyResult(PromoteKeyResult.RESULT_OK, log, promotedRing.getMasterKeyId()); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java new file mode 100644 index 000000000..e98f6b150 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java @@ -0,0 +1,136 @@ +package org.sufficientlysecure.keychain.operations; + +import android.content.Context; +import android.net.Uri; + +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; +import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; +import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.ProgressScaler; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +/** This is a high-level operation, which encapsulates one or more sign/encrypt + * operations, using URIs or byte arrays as input and output. + * + * This operation is fail-fast: If any sign/encrypt sub-operation fails or returns + * a pending result, it will terminate. + * + */ +public class SignEncryptOperation extends BaseOperation { + + public SignEncryptOperation(Context context, ProviderHelper providerHelper, + Progressable progressable, AtomicBoolean cancelled) { + super(context, providerHelper, progressable, cancelled); + } + + public SignEncryptResult execute(SignEncryptParcel input) { + + OperationLog log = new OperationLog(); + log.add(LogType.MSG_SE, 0); + + ArrayDeque<Uri> inputUris = new ArrayDeque<>(input.getInputUris()); + ArrayDeque<Uri> outputUris = new ArrayDeque<>(input.getOutputUris()); + byte[] inputBytes = input.getBytes(); + byte[] outputBytes = null; + + ArrayList<PgpSignEncryptResult> results = new ArrayList<>(); + + do { + + if (checkCancelled()) { + log.add(LogType.MSG_OPERATION_CANCELLED, 0); + return new SignEncryptResult(SignEncryptResult.RESULT_CANCELLED, log, results); + } + + InputData inputData; + { + if (inputBytes != null) { + log.add(LogType.MSG_SE_INPUT_BYTES, 1); + InputStream is = new ByteArrayInputStream(inputBytes); + inputData = new InputData(is, inputBytes.length); + inputBytes = null; + } else { + if (inputUris.isEmpty()) { + log.add(LogType.MSG_SE_ERROR_NO_INPUT, 1); + return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); + } + + log.add(LogType.MSG_SE_INPUT_URI, 1); + Uri uri = inputUris.removeFirst(); + try { + InputStream is = mContext.getContentResolver().openInputStream(uri); + long fileSize = FileHelper.getFileSize(mContext, uri, 0); + String filename = FileHelper.getFilename(mContext, uri); + inputData = new InputData(is, fileSize, filename); + } catch (FileNotFoundException e) { + log.add(LogType.MSG_SE_ERROR_INPUT_URI_NOT_FOUND, 1); + return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); + } + } + } + + OutputStream outStream; + { + if (!outputUris.isEmpty()) { + try { + Uri outputUri = outputUris.removeFirst(); + outStream = mContext.getContentResolver().openOutputStream(outputUri); + } catch (FileNotFoundException e) { + log.add(LogType.MSG_SE_ERROR_OUTPUT_URI_NOT_FOUND, 1); + return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); + } + } else { + if (outputBytes != null) { + log.add(LogType.MSG_SE_ERROR_TOO_MANY_INPUTS, 1); + return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); + } + outStream = new ByteArrayOutputStream(); + } + } + + PgpSignEncryptOperation op = new PgpSignEncryptOperation(mContext, mProviderHelper, + new ProgressScaler(), mCancelled); + PgpSignEncryptResult result = op.execute(input, inputData, outStream); + results.add(result); + log.add(result, 2); + + if (result.isPending()) { + return new SignEncryptResult(SignEncryptResult.RESULT_PENDING, log, results); + } + + if (!result.success()) { + return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); + } + + if (outStream instanceof ByteArrayOutputStream) { + outputBytes = ((ByteArrayOutputStream) outStream).toByteArray(); + } + + } while (!inputUris.isEmpty()); + + if (!outputUris.isEmpty()) { + // Any output URIs left are indicative of a programming error + log.add(LogType.MSG_SE_WARN_OUTPUT_LEFT, 1); + } + + log.add(LogType.MSG_SE_SUCCESS, 1); + return new SignEncryptResult(SignEncryptResult.RESULT_OK, log, results, outputBytes); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java index 7b38cd244..94684851a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java @@ -21,18 +21,14 @@ package org.sufficientlysecure.keychain.operations.results; import android.app.Activity; import android.content.Intent; import android.os.Parcel; -import android.os.Parcelable; -import android.view.View; - -import com.github.johnpersano.supertoasts.SuperCardToast; -import com.github.johnpersano.supertoasts.SuperToast; -import com.github.johnpersano.supertoasts.SuperToast.Duration; -import com.github.johnpersano.supertoasts.util.OnClickWrapper; -import com.github.johnpersano.supertoasts.util.Style; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; +import org.sufficientlysecure.keychain.ui.util.Notify.Showable; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; public class CertifyResult extends OperationResult { @@ -78,30 +74,31 @@ public class CertifyResult extends OperationResult { } }; - public SuperCardToast createNotify(final Activity activity) { + public Showable createNotify(final Activity activity) { int resultType = getResult(); String str; - int duration, color; + int duration; + Style style; // Not an overall failure if ((resultType & OperationResult.RESULT_ERROR) == 0) { String withWarnings; - duration = Duration.EXTRA_LONG; - color = Style.GREEN; + duration = Notify.LENGTH_LONG; + style = Style.OK; withWarnings = ""; // Any warnings? if ((resultType & ImportKeyResult.RESULT_WARNINGS) > 0) { duration = 0; - color = Style.ORANGE; + style = Style.WARN; withWarnings += activity.getString(R.string.with_warnings); } if ((resultType & ImportKeyResult.RESULT_CANCELLED) > 0) { duration = 0; - color = Style.ORANGE; + style = Style.WARN; withWarnings += activity.getString(R.string.with_cancelled); } @@ -111,46 +108,27 @@ public class CertifyResult extends OperationResult { if (mCertifyError > 0) { // definitely switch to warning-style message in this case! duration = 0; - color = Style.RED; + style = Style.ERROR; str += " " + activity.getResources().getQuantityString( R.plurals.certify_keys_with_errors, mCertifyError, mCertifyError); } } else { duration = 0; - color = Style.RED; + style = Style.ERROR; str = activity.getResources().getQuantityString(R.plurals.certify_error, mCertifyError, mCertifyError); } - boolean button = getLog() != null && !getLog().isEmpty(); - SuperCardToast toast = new SuperCardToast(activity, - button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD, - Style.getStyle(color, SuperToast.Animations.POPUP)); - toast.setText(str); - toast.setDuration(duration); - toast.setIndeterminate(duration == 0); - toast.setSwipeToDismiss(true); - // If we have a log and it's non-empty, show a View Log button - if (button) { - toast.setButtonIcon(R.drawable.ic_action_view_as_list, - activity.getResources().getString(R.string.view_log)); - toast.setButtonTextColor(activity.getResources().getColor(R.color.black)); - toast.setTextColor(activity.getResources().getColor(R.color.black)); - toast.setOnClickWrapper(new OnClickWrapper("supercardtoast", - new SuperToast.OnClickListener() { - @Override - public void onClick(View view, Parcelable token) { - Intent intent = new Intent( - activity, LogDisplayActivity.class); - intent.putExtra(LogDisplayFragment.EXTRA_RESULT, CertifyResult.this); - activity.startActivity(intent); - } - } - )); - } - - return toast; + return Notify.createNotify(activity, str, duration, style, new ActionListener() { + @Override + public void onAction() { + Intent intent = new Intent( + activity, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, CertifyResult.this); + activity.startActivity(intent); + } + }, R.string.view_log); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java index d81577102..86b37fea6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java @@ -41,6 +41,9 @@ public class DecryptVerifyResult extends OperationResult { OpenPgpSignatureResult mSignatureResult; OpenPgpMetadata mDecryptMetadata; + // This holds the charset which was specified in the ascii armor, if specified + // https://tools.ietf.org/html/rfc4880#page56 + String mCharset; public long getKeyIdPassphraseNeeded() { return mKeyIdPassphraseNeeded; @@ -84,6 +87,14 @@ public class DecryptVerifyResult extends OperationResult { mDecryptMetadata = decryptMetadata; } + public String getCharset () { + return mCharset; + } + + public void setCharset(String charset) { + mCharset = charset; + } + public boolean isPending() { return (mResult & RESULT_PENDING) == RESULT_PENDING; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java index 1ca5ad20a..62197541a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java @@ -21,18 +21,14 @@ package org.sufficientlysecure.keychain.operations.results; import android.app.Activity; import android.content.Intent; import android.os.Parcel; -import android.os.Parcelable; -import android.view.View; - -import com.github.johnpersano.supertoasts.SuperCardToast; -import com.github.johnpersano.supertoasts.SuperToast; -import com.github.johnpersano.supertoasts.SuperToast.Duration; -import com.github.johnpersano.supertoasts.util.OnClickWrapper; -import com.github.johnpersano.supertoasts.util.Style; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; +import org.sufficientlysecure.keychain.ui.util.Notify.Showable; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; public class DeleteResult extends OperationResult { @@ -68,31 +64,32 @@ public class DeleteResult extends OperationResult { } }; - public SuperCardToast createNotify(final Activity activity) { + public Showable createNotify(final Activity activity) { int resultType = getResult(); String str; - int duration, color; + int duration; + Style style; // Not an overall failure if ((resultType & OperationResult.RESULT_ERROR) == 0) { String untilCancelled; - duration = Duration.EXTRA_LONG; - color = Style.GREEN; + duration = Notify.LENGTH_LONG; + style = Style.OK; untilCancelled = ""; // Any warnings? if ((resultType & ImportKeyResult.RESULT_CANCELLED) > 0) { duration = 0; - color = Style.ORANGE; + style = Style.WARN; untilCancelled += activity.getString(R.string.with_cancelled); } // New and updated keys if (mOk > 0 && mFail > 0) { - color = Style.ORANGE; + style = Style.WARN; duration = 0; str = activity.getResources().getQuantityString( R.plurals.delete_ok_but_fail_1, mOk, mOk); @@ -105,13 +102,13 @@ public class DeleteResult extends OperationResult { str = activity.getString(R.string.delete_cancelled); } else { duration = 0; - color = Style.RED; + style = Style.ERROR; str = "internal error"; } } else { duration = 0; - color = Style.RED; + style = Style.ERROR; if (mFail == 0) { str = activity.getString(R.string.delete_nothing); } else { @@ -119,34 +116,15 @@ public class DeleteResult extends OperationResult { } } - boolean button = getLog() != null && !getLog().isEmpty(); - SuperCardToast toast = new SuperCardToast(activity, - button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD, - Style.getStyle(color, SuperToast.Animations.POPUP)); - toast.setText(str); - toast.setDuration(duration); - toast.setIndeterminate(duration == 0); - toast.setSwipeToDismiss(true); - // If we have a log and it's non-empty, show a View Log button - if (button) { - toast.setButtonIcon(R.drawable.ic_action_view_as_list, - activity.getResources().getString(R.string.view_log)); - toast.setButtonTextColor(activity.getResources().getColor(R.color.black)); - toast.setTextColor(activity.getResources().getColor(R.color.black)); - toast.setOnClickWrapper(new OnClickWrapper("supercardtoast", - new SuperToast.OnClickListener() { - @Override - public void onClick(View view, Parcelable token) { - Intent intent = new Intent( - activity, LogDisplayActivity.class); - intent.putExtra(LogDisplayFragment.EXTRA_RESULT, DeleteResult.this); - activity.startActivity(intent); - } - } - )); - } - - return toast; + return Notify.createNotify(activity, str, duration, style, new ActionListener() { + @Override + public void onAction() { + Intent intent = new Intent( + activity, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, DeleteResult.this); + activity.startActivity(intent); + } + }, R.string.view_log); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java index f2acfef1e..abcf575af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java @@ -20,34 +20,24 @@ package org.sufficientlysecure.keychain.operations.results; import android.os.Parcel; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; - public class EditKeyResult extends OperationResult { - private transient UncachedKeyRing mRing; - public final long mRingMasterKeyId; + public final Long mMasterKeyId; - public EditKeyResult(int result, OperationLog log, - UncachedKeyRing ring) { + public EditKeyResult(int result, OperationLog log, Long masterKeyId) { super(result, log); - mRing = ring; - mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none; - } - - public UncachedKeyRing getRing() { - return mRing; + mMasterKeyId = masterKeyId; } public EditKeyResult(Parcel source) { super(source); - mRingMasterKeyId = source.readLong(); + mMasterKeyId = source.readLong(); } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeLong(mRingMasterKeyId); + dest.writeLong(mMasterKeyId); } public static Creator<EditKeyResult> CREATOR = new Creator<EditKeyResult>() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java index 1dd038a23..2d533ed16 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java @@ -21,18 +21,14 @@ package org.sufficientlysecure.keychain.operations.results; import android.app.Activity; import android.content.Intent; import android.os.Parcel; -import android.os.Parcelable; -import android.view.View; - -import com.github.johnpersano.supertoasts.SuperCardToast; -import com.github.johnpersano.supertoasts.SuperToast; -import com.github.johnpersano.supertoasts.SuperToast.Duration; -import com.github.johnpersano.supertoasts.util.OnClickWrapper; -import com.github.johnpersano.supertoasts.util.Style; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; +import org.sufficientlysecure.keychain.ui.util.Notify.Showable; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; public class ImportKeyResult extends OperationResult { @@ -83,6 +79,10 @@ public class ImportKeyResult extends OperationResult { mImportedMasterKeyIds = source.createLongArray(); } + public ImportKeyResult(int result, OperationLog log) { + this(result, log, 0, 0, 0, 0, new long[] { }); + } + public ImportKeyResult(int result, OperationLog log, int newKeys, int updatedKeys, int badKeys, int secret, long[] importedMasterKeyIds) { @@ -114,30 +114,31 @@ public class ImportKeyResult extends OperationResult { } }; - public SuperCardToast createNotify(final Activity activity) { + public Showable createNotify(final Activity activity) { int resultType = getResult(); String str; - int duration, color; + int duration; + Style style; // Not an overall failure if ((resultType & OperationResult.RESULT_ERROR) == 0) { String withWarnings; - duration = Duration.EXTRA_LONG; - color = Style.GREEN; + duration = Notify.LENGTH_LONG; + style = Style.OK; withWarnings = ""; // Any warnings? if ((resultType & ImportKeyResult.RESULT_WARNINGS) > 0) { duration = 0; - color = Style.ORANGE; + style = Style.WARN; withWarnings += activity.getString(R.string.with_warnings); } if ((resultType & ImportKeyResult.RESULT_CANCELLED) > 0) { duration = 0; - color = Style.ORANGE; + style = Style.WARN; withWarnings += activity.getString(R.string.with_cancelled); } @@ -155,20 +156,20 @@ public class ImportKeyResult extends OperationResult { R.plurals.import_keys_added, mNewKeys, mNewKeys, withWarnings); } else { duration = 0; - color = Style.RED; + style = Style.ERROR; str = "internal error"; } if (isOkWithErrors()) { // definitely switch to warning-style message in this case! duration = 0; - color = Style.RED; + style = Style.ERROR; str += " " + activity.getResources().getQuantityString( R.plurals.import_keys_with_errors, mBadKeys, mBadKeys); } } else { duration = 0; - color = Style.RED; + style = Style.ERROR; if (isFailNothing()) { str = activity.getString((resultType & ImportKeyResult.RESULT_CANCELLED) > 0 ? R.string.import_error_nothing_cancelled @@ -178,34 +179,15 @@ public class ImportKeyResult extends OperationResult { } } - boolean button = getLog() != null && !getLog().isEmpty(); - SuperCardToast toast = new SuperCardToast(activity, - button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD, - Style.getStyle(color, SuperToast.Animations.POPUP)); - toast.setText(str); - toast.setDuration(duration); - toast.setIndeterminate(duration == 0); - toast.setSwipeToDismiss(true); - // If we have a log and it's non-empty, show a View Log button - if (button) { - toast.setButtonIcon(R.drawable.ic_action_view_as_list, - activity.getResources().getString(R.string.view_log)); - toast.setButtonTextColor(activity.getResources().getColor(R.color.black)); - toast.setTextColor(activity.getResources().getColor(R.color.black)); - toast.setOnClickWrapper(new OnClickWrapper("supercardtoast", - new SuperToast.OnClickListener() { - @Override - public void onClick(View view, Parcelable token) { - Intent intent = new Intent( - activity, LogDisplayActivity.class); - intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ImportKeyResult.this); - activity.startActivity(intent); - } - } - )); - } - - return toast; + return Notify.createNotify(activity, str, duration, style, new ActionListener() { + @Override + public void onAction() { + Intent intent = new Intent( + activity, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ImportKeyResult.this); + activity.startActivity(intent); + } + }, R.string.view_log); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 70d999242..f79900aab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -22,17 +22,15 @@ 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.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; +import org.sufficientlysecure.keychain.ui.util.Notify.Showable; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -195,58 +193,44 @@ public abstract class OperationResult implements Parcelable { } - public SuperCardToast createNotify(final Activity activity) { - - int color; + public Showable createNotify(final Activity activity) { Log.d(Constants.TAG, "mLog.getLast()"+mLog.getLast()); Log.d(Constants.TAG, "mLog.getLast().mType"+mLog.getLast().mType); Log.d(Constants.TAG, "mLog.getLast().mType.getMsgId()"+mLog.getLast().mType.getMsgId()); // Take the last message as string - String str = activity.getString(mLog.getLast().mType.getMsgId()); + int msgId = mLog.getLast().mType.getMsgId(); + + Style style; // Not an overall failure if (cancelled()) { - color = Style.RED; + style = Style.ERROR; } else if (success()) { if (getLog().containsWarnings()) { - color = Style.ORANGE; + style = Style.WARN; } else { - color = Style.GREEN; + style = Style.OK; } } else { - color = Style.RED; + style = Style.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(SuperToast.Duration.EXTRA_LONG); - toast.setIndeterminate(false); - toast.setSwipeToDismiss(true); - // If we have a log and it's non-empty, show a View Log button - if (button) { - toast.setButtonIcon(R.drawable.ic_action_view_as_list, - activity.getResources().getString(R.string.view_log)); - toast.setButtonTextColor(activity.getResources().getColor(R.color.black)); - toast.setTextColor(activity.getResources().getColor(R.color.black)); - toast.setOnClickWrapper(new OnClickWrapper("supercardtoast", - new SuperToast.OnClickListener() { - @Override - public void onClick(View view, Parcelable token) { - Intent intent = new Intent( - activity, LogDisplayActivity.class); - intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this); - activity.startActivity(intent); - } - } - )); + if (getLog() == null || getLog().isEmpty()) { + return Notify.createNotify(activity, msgId, Notify.LENGTH_LONG, style); } - return toast; + return Notify.createNotify(activity, msgId, Notify.LENGTH_LONG, style, + new ActionListener() { + @Override + public void onAction() { + Intent intent = new Intent( + activity, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this); + activity.startActivity(intent); + } + }, R.string.view_log); } @@ -343,6 +327,18 @@ public abstract class OperationResult implements Parcelable { MSG_IP_UID_REORDER(LogLevel.DEBUG, R.string.msg_ip_uid_reorder), MSG_IP_UID_PROCESSING (LogLevel.DEBUG, R.string.msg_ip_uid_processing), MSG_IP_UID_REVOKED (LogLevel.DEBUG, R.string.msg_ip_uid_revoked), + MSG_IP_UAT_CLASSIFYING (LogLevel.DEBUG, R.string.msg_ip_uat_classifying), + MSG_IP_UAT_PROCESSING_IMAGE (LogLevel.DEBUG, R.string.msg_ip_uat_processing_image), + MSG_IP_UAT_PROCESSING_UNKNOWN (LogLevel.DEBUG, R.string.msg_ip_uat_processing_unknown), + MSG_IP_UAT_REVOKED (LogLevel.DEBUG, R.string.msg_ip_uat_revoked), + MSG_IP_UAT_CERT_BAD (LogLevel.WARN, R.string.msg_ip_uat_cert_bad), + MSG_IP_UAT_CERT_OLD (LogLevel.DEBUG, R.string.msg_ip_uat_cert_old), + MSG_IP_UAT_CERT_NONREVOKE (LogLevel.DEBUG, R.string.msg_ip_uat_cert_nonrevoke), + MSG_IP_UAT_CERT_NEW (LogLevel.DEBUG, R.string.msg_ip_uat_cert_new), + MSG_IP_UAT_CERT_ERROR (LogLevel.WARN, R.string.msg_ip_uat_cert_error), + MSG_IP_UAT_CERTS_UNKNOWN (LogLevel.DEBUG, R.plurals.msg_ip_uat_certs_unknown), + MSG_IP_UAT_CERT_GOOD_REVOKE (LogLevel.DEBUG, R.string.msg_ip_uat_cert_good_revoke), + MSG_IP_UAT_CERT_GOOD (LogLevel.DEBUG, R.string.msg_ip_uat_cert_good), // import secret MSG_IS(LogLevel.START, R.string.msg_is), @@ -359,6 +355,7 @@ public abstract class OperationResult implements Parcelable { MSG_IS_SUBKEY_STRIPPED (LogLevel.DEBUG, R.string.msg_is_subkey_stripped), MSG_IS_SUBKEY_DIVERT (LogLevel.DEBUG, R.string.msg_is_subkey_divert), MSG_IS_SUBKEY_EMPTY (LogLevel.DEBUG, R.string.msg_is_subkey_empty), + MSG_IS_SUBKEY_PIN (LogLevel.DEBUG, R.string.msg_is_subkey_pin), MSG_IS_SUCCESS_IDENTICAL (LogLevel.OK, R.string.msg_is_success_identical), MSG_IS_SUCCESS (LogLevel.OK, R.string.msg_is_success), @@ -370,13 +367,16 @@ public abstract class OperationResult implements Parcelable { MSG_KC_ERROR_MASTER_ALGO (LogLevel.ERROR, R.string.msg_kc_error_master_algo), MSG_KC_ERROR_DUP_KEY (LogLevel.ERROR, R.string.msg_kc_error_dup_key), MSG_KC_MASTER (LogLevel.DEBUG, R.string.msg_kc_master), - MSG_KC_REVOKE_BAD_ERR (LogLevel.WARN, R.string.msg_kc_revoke_bad_err), - MSG_KC_REVOKE_BAD_LOCAL (LogLevel.WARN, R.string.msg_kc_revoke_bad_local), - MSG_KC_REVOKE_BAD_TIME (LogLevel.WARN, R.string.msg_kc_revoke_bad_time), - MSG_KC_REVOKE_BAD_TYPE (LogLevel.WARN, R.string.msg_kc_revoke_bad_type), - MSG_KC_REVOKE_BAD_TYPE_UID (LogLevel.WARN, R.string.msg_kc_revoke_bad_type_uid), - MSG_KC_REVOKE_BAD (LogLevel.WARN, R.string.msg_kc_revoke_bad), + MSG_KC_MASTER_BAD_TYPE(LogLevel.WARN, R.string.msg_kc_master_bad_type), + MSG_KC_MASTER_BAD_LOCAL(LogLevel.WARN, R.string.msg_kc_master_bad_local), + MSG_KC_MASTER_BAD_ERR(LogLevel.WARN, R.string.msg_kc_master_bad_err), + MSG_KC_MASTER_BAD_TIME(LogLevel.WARN, R.string.msg_kc_master_bad_time), + MSG_KC_MASTER_BAD_TYPE_UID(LogLevel.WARN, R.string.msg_kc_master_bad_type_uid), + MSG_KC_MASTER_BAD(LogLevel.WARN, R.string.msg_kc_master_bad), + MSG_KC_MASTER_LOCAL(LogLevel.WARN, R.string.msg_kc_master_local), MSG_KC_REVOKE_DUP (LogLevel.DEBUG, R.string.msg_kc_revoke_dup), + MSG_KC_NOTATION_DUP (LogLevel.DEBUG, R.string.msg_kc_notation_dup), + MSG_KC_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_kc_notation_empty), MSG_KC_SUB (LogLevel.DEBUG, R.string.msg_kc_sub), MSG_KC_SUB_BAD(LogLevel.WARN, R.string.msg_kc_sub_bad), MSG_KC_SUB_BAD_ERR(LogLevel.WARN, R.string.msg_kc_sub_bad_err), @@ -412,6 +412,21 @@ public abstract class OperationResult implements Parcelable { MSG_KC_UID_REVOKE_OLD (LogLevel.DEBUG, R.string.msg_kc_uid_revoke_old), MSG_KC_UID_REMOVE (LogLevel.DEBUG, R.string.msg_kc_uid_remove), MSG_KC_UID_WARN_ENCODING (LogLevel.WARN, R.string.msg_kc_uid_warn_encoding), + MSG_KC_UAT_JPEG (LogLevel.DEBUG, R.string.msg_kc_uat_jpeg), + MSG_KC_UAT_UNKNOWN (LogLevel.DEBUG, R.string.msg_kc_uat_unknown), + MSG_KC_UAT_BAD_ERR (LogLevel.WARN, R.string.msg_kc_uat_bad_err), + MSG_KC_UAT_BAD_LOCAL (LogLevel.WARN, R.string.msg_kc_uat_bad_local), + MSG_KC_UAT_BAD_TIME (LogLevel.WARN, R.string.msg_kc_uat_bad_time), + MSG_KC_UAT_BAD_TYPE (LogLevel.WARN, R.string.msg_kc_uat_bad_type), + MSG_KC_UAT_BAD (LogLevel.WARN, R.string.msg_kc_uat_bad), + MSG_KC_UAT_CERT_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_cert_dup), + MSG_KC_UAT_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_dup), + MSG_KC_UAT_FOREIGN (LogLevel.DEBUG, R.string.msg_kc_uat_foreign), + MSG_KC_UAT_NO_CERT (LogLevel.DEBUG, R.string.msg_kc_uat_no_cert), + MSG_KC_UAT_REVOKE_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_revoke_dup), + MSG_KC_UAT_REVOKE_OLD (LogLevel.DEBUG, R.string.msg_kc_uat_revoke_old), + MSG_KC_UAT_REMOVE (LogLevel.DEBUG, R.string.msg_kc_uat_remove), + MSG_KC_UAT_WARN_ENCODING (LogLevel.WARN, R.string.msg_kc_uat_warn_encoding), // keyring consolidation @@ -442,6 +457,7 @@ public abstract class OperationResult implements Parcelable { // secret key modify MSG_MF (LogLevel.START, R.string.msg_mr), + MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial), MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode), MSG_MF_ERROR_FINGERPRINT (LogLevel.ERROR, R.string.msg_mf_error_fingerprint), MSG_MF_ERROR_KEYID (LogLevel.ERROR, R.string.msg_mf_error_keyid), @@ -454,10 +470,13 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_PASSPHRASE_MASTER(LogLevel.ERROR, R.string.msg_mf_error_passphrase_master), MSG_MF_ERROR_PAST_EXPIRY(LogLevel.ERROR, R.string.msg_mf_error_past_expiry), MSG_MF_ERROR_PGP (LogLevel.ERROR, R.string.msg_mf_error_pgp), + MSG_MF_ERROR_RESTRICTED(LogLevel.ERROR, R.string.msg_mf_error_restricted), MSG_MF_ERROR_REVOKED_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_revoked_primary), MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig), MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing), MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master), + MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin), + MSG_MF_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty), MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase), MSG_MF_PASSPHRASE_KEY (LogLevel.DEBUG, R.string.msg_mf_passphrase_key), MSG_MF_PASSPHRASE_EMPTY_RETRY (LogLevel.DEBUG, R.string.msg_mf_passphrase_empty_retry), @@ -474,6 +493,9 @@ public abstract class OperationResult implements Parcelable { MSG_MF_UID_PRIMARY (LogLevel.INFO, R.string.msg_mf_uid_primary), MSG_MF_UID_REVOKE (LogLevel.INFO, R.string.msg_mf_uid_revoke), MSG_MF_UID_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_mf_uid_error_empty), + MSG_MF_UAT_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_mf_uat_error_empty), + MSG_MF_UAT_ADD_IMAGE (LogLevel.INFO, R.string.msg_mf_uat_add_image), + MSG_MF_UAT_ADD_UNKNOWN (LogLevel.INFO, R.string.msg_mf_uat_add_unknown), MSG_MF_UNLOCK_ERROR (LogLevel.ERROR, R.string.msg_mf_unlock_error), MSG_MF_UNLOCK (LogLevel.DEBUG, R.string.msg_mf_unlock), @@ -491,6 +513,7 @@ public abstract class OperationResult implements Parcelable { MSG_CON_ERROR_PUBLIC (LogLevel.ERROR, R.string.msg_con_error_public), MSG_CON_ERROR_SECRET (LogLevel.ERROR, R.string.msg_con_error_secret), MSG_CON_RECOVER (LogLevel.DEBUG, R.string.msg_con_recover), + MSG_CON_RECURSIVE (LogLevel.OK, R.string.msg_con_recursive), MSG_CON_REIMPORT_PUBLIC (LogLevel.DEBUG, R.plurals.msg_con_reimport_public), MSG_CON_REIMPORT_PUBLIC_SKIP (LogLevel.DEBUG, R.string.msg_con_reimport_public_skip), MSG_CON_REIMPORT_SECRET (LogLevel.DEBUG, R.plurals.msg_con_reimport_secret), @@ -502,6 +525,21 @@ public abstract class OperationResult implements Parcelable { MSG_CON_WARN_DELETE_PUBLIC (LogLevel.WARN, R.string.msg_con_warn_delete_public), MSG_CON_WARN_DELETE_SECRET (LogLevel.WARN, R.string.msg_con_warn_delete_secret), + // edit key (higher level operation than modify) + MSG_ED (LogLevel.START, R.string.msg_ed), + MSG_ED_CACHING_NEW (LogLevel.DEBUG, R.string.msg_ed_caching_new), + MSG_ED_ERROR_NO_PARCEL (LogLevel.ERROR, R.string.msg_ed_error_no_parcel), + MSG_ED_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_ed_error_key_not_found), + MSG_ED_FETCHING (LogLevel.DEBUG, R.string.msg_ed_fetching), + MSG_ED_SUCCESS (LogLevel.OK, R.string.msg_ed_success), + + // promote key + MSG_PR (LogLevel.START, R.string.msg_pr), + MSG_PR_ERROR_ALREADY_SECRET (LogLevel.ERROR, R.string.msg_pr_error_already_secret), + MSG_PR_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_pr_error_key_not_found), + MSG_PR_FETCHING (LogLevel.DEBUG, R.string.msg_pr_fetching), + MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success), + // messages used in UI code MSG_EK_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_ek_error_divert), MSG_EK_ERROR_DUMMY (LogLevel.ERROR, R.string.msg_ek_error_dummy), @@ -511,11 +549,13 @@ public abstract class OperationResult implements Parcelable { MSG_DC_ASKIP_NO_KEY (LogLevel.DEBUG, R.string.msg_dc_askip_no_key), MSG_DC_ASKIP_NOT_ALLOWED (LogLevel.DEBUG, R.string.msg_dc_askip_not_allowed), MSG_DC_ASYM (LogLevel.DEBUG, R.string.msg_dc_asym), + MSG_DC_CHARSET (LogLevel.DEBUG, R.string.msg_dc_charset), MSG_DC_CLEAR_DATA (LogLevel.DEBUG, R.string.msg_dc_clear_data), MSG_DC_CLEAR_DECOMPRESS (LogLevel.DEBUG, R.string.msg_dc_clear_decompress), MSG_DC_CLEAR_META_FILE (LogLevel.DEBUG, R.string.msg_dc_clear_meta_file), MSG_DC_CLEAR_META_MIME (LogLevel.DEBUG, R.string.msg_dc_clear_meta_mime), MSG_DC_CLEAR_META_SIZE (LogLevel.DEBUG, R.string.msg_dc_clear_meta_size), + MSG_DC_CLEAR_META_SIZE_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_clear_meta_size_unknown), MSG_DC_CLEAR_META_TIME (LogLevel.DEBUG, R.string.msg_dc_clear_meta_time), MSG_DC_CLEAR (LogLevel.DEBUG, R.string.msg_dc_clear), MSG_DC_CLEAR_SIGNATURE_BAD (LogLevel.WARN, R.string.msg_dc_clear_signature_bad), @@ -525,6 +565,7 @@ public abstract class OperationResult implements Parcelable { MSG_DC_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_dc_error_bad_passphrase), MSG_DC_ERROR_EXTRACT_KEY (LogLevel.ERROR, R.string.msg_dc_error_extract_key), MSG_DC_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_dc_error_integrity_check), + MSG_DC_ERROR_INTEGRITY_MISSING (LogLevel.ERROR, R.string.msg_dc_error_integrity_missing), MSG_DC_ERROR_INVALID_SIGLIST(LogLevel.ERROR, R.string.msg_dc_error_invalid_siglist), MSG_DC_ERROR_IO (LogLevel.ERROR, R.string.msg_dc_error_io), MSG_DC_ERROR_NO_DATA (LogLevel.ERROR, R.string.msg_dc_error_no_data), @@ -555,32 +596,47 @@ public abstract class OperationResult implements Parcelable { MSG_VL_OK (LogLevel.OK, R.string.msg_vl_ok), // signencrypt - MSG_SE_ASYMMETRIC (LogLevel.INFO, R.string.msg_se_asymmetric), - MSG_SE_CLEARSIGN_ONLY (LogLevel.DEBUG, R.string.msg_se_clearsign_only), - MSG_SE_COMPRESSING (LogLevel.DEBUG, R.string.msg_se_compressing), - MSG_SE_ENCRYPTING (LogLevel.DEBUG, R.string.msg_se_encrypting), - MSG_SE_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_se_error_bad_passphrase), - MSG_SE_ERROR_IO (LogLevel.ERROR, R.string.msg_se_error_io), - MSG_SE_ERROR_SIGN_KEY(LogLevel.ERROR, R.string.msg_se_error_sign_key), - MSG_SE_ERROR_KEY_SIGN (LogLevel.ERROR, R.string.msg_se_error_key_sign), - MSG_SE_ERROR_NFC (LogLevel.ERROR, R.string.msg_se_error_nfc), - MSG_SE_ERROR_PGP (LogLevel.ERROR, R.string.msg_se_error_pgp), - MSG_SE_ERROR_SIG (LogLevel.ERROR, R.string.msg_se_error_sig), - MSG_SE_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_se_error_unlock), - MSG_SE_KEY_OK (LogLevel.OK, R.string.msg_se_key_ok), - MSG_SE_KEY_UNKNOWN (LogLevel.DEBUG, R.string.msg_se_key_unknown), - MSG_SE_KEY_WARN (LogLevel.WARN, R.string.msg_se_key_warn), - MSG_SE_OK (LogLevel.OK, R.string.msg_se_ok), - MSG_SE_PENDING_NFC (LogLevel.INFO, R.string.msg_se_pending_nfc), - MSG_SE_PENDING_PASSPHRASE (LogLevel.INFO, R.string.msg_se_pending_passphrase), - MSG_SE (LogLevel.DEBUG, R.string.msg_se), - MSG_SE_SIGNING (LogLevel.DEBUG, R.string.msg_se_signing), - MSG_SE_SIGCRYPTING (LogLevel.DEBUG, R.string.msg_se_sigcrypting), - MSG_SE_SYMMETRIC (LogLevel.INFO, R.string.msg_se_symmetric), + MSG_SE (LogLevel.START, R.string.msg_se), + MSG_SE_INPUT_BYTES (LogLevel.INFO, R.string.msg_se_input_bytes), + MSG_SE_INPUT_URI (LogLevel.INFO, R.string.msg_se_input_uri), + MSG_SE_ERROR_NO_INPUT (LogLevel.DEBUG, R.string.msg_se_error_no_input), + MSG_SE_ERROR_INPUT_URI_NOT_FOUND (LogLevel.ERROR, R.string.msg_se_error_input_uri_not_found), + MSG_SE_ERROR_OUTPUT_URI_NOT_FOUND (LogLevel.ERROR, R.string.msg_se_error_output_uri_not_found), + MSG_SE_ERROR_TOO_MANY_INPUTS (LogLevel.ERROR, R.string.msg_se_error_too_many_inputs), + MSG_SE_WARN_OUTPUT_LEFT (LogLevel.WARN, R.string.msg_se_warn_output_left), + MSG_SE_SUCCESS (LogLevel.OK, R.string.msg_se_success), + + // pgpsignencrypt + MSG_PSE_ASYMMETRIC (LogLevel.INFO, R.string.msg_pse_asymmetric), + MSG_PSE_CLEARSIGN_ONLY (LogLevel.DEBUG, R.string.msg_pse_clearsign_only), + MSG_PSE_COMPRESSING (LogLevel.DEBUG, R.string.msg_pse_compressing), + MSG_PSE_ENCRYPTING (LogLevel.DEBUG, R.string.msg_pse_encrypting), + MSG_PSE_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_pse_error_bad_passphrase), + MSG_PSE_ERROR_HASH_ALGO (LogLevel.ERROR, R.string.msg_pse_error_hash_algo), + MSG_PSE_ERROR_IO (LogLevel.ERROR, R.string.msg_pse_error_io), + MSG_PSE_ERROR_SIGN_KEY(LogLevel.ERROR, R.string.msg_pse_error_sign_key), + MSG_PSE_ERROR_KEY_SIGN (LogLevel.ERROR, R.string.msg_pse_error_key_sign), + MSG_PSE_ERROR_NFC (LogLevel.ERROR, R.string.msg_pse_error_nfc), + MSG_PSE_ERROR_PGP (LogLevel.ERROR, R.string.msg_pse_error_pgp), + MSG_PSE_ERROR_SIG (LogLevel.ERROR, R.string.msg_pse_error_sig), + MSG_PSE_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_pse_error_unlock), + MSG_PSE_KEY_OK (LogLevel.OK, R.string.msg_pse_key_ok), + MSG_PSE_KEY_UNKNOWN (LogLevel.DEBUG, R.string.msg_pse_key_unknown), + MSG_PSE_KEY_WARN (LogLevel.WARN, R.string.msg_pse_key_warn), + MSG_PSE_OK (LogLevel.OK, R.string.msg_pse_ok), + MSG_PSE_PENDING_NFC (LogLevel.INFO, R.string.msg_pse_pending_nfc), + MSG_PSE_PENDING_PASSPHRASE (LogLevel.INFO, R.string.msg_pse_pending_passphrase), + MSG_PSE (LogLevel.DEBUG, R.string.msg_pse), + MSG_PSE_SIGNING (LogLevel.DEBUG, R.string.msg_pse_signing), + MSG_PSE_SIGNING_CLEARTEXT (LogLevel.DEBUG, R.string.msg_pse_signing_cleartext), + MSG_PSE_SIGNING_DETACHED (LogLevel.DEBUG, R.string.msg_pse_signing_detached), + MSG_PSE_SIGCRYPTING (LogLevel.DEBUG, R.string.msg_pse_sigcrypting), + MSG_PSE_SYMMETRIC (LogLevel.INFO, R.string.msg_pse_symmetric), MSG_CRT_CERTIFYING (LogLevel.DEBUG, R.string.msg_crt_certifying), MSG_CRT_CERTIFY_ALL (LogLevel.DEBUG, R.string.msg_crt_certify_all), MSG_CRT_CERTIFY_SOME (LogLevel.DEBUG, R.plurals.msg_crt_certify_some), + MSG_CRT_ERROR_SELF (LogLevel.ERROR, R.string.msg_crt_error_self), MSG_CRT_ERROR_MASTER_NOT_FOUND (LogLevel.ERROR, R.string.msg_crt_error_master_not_found), MSG_CRT_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_crt_error_nothing), MSG_CRT_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_crt_error_unlock), @@ -608,6 +664,7 @@ public abstract class OperationResult implements Parcelable { MSG_IMPORT_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_import_fingerprint_error), MSG_IMPORT_FINGERPRINT_OK (LogLevel.DEBUG, R.string.msg_import_fingerprint_ok), MSG_IMPORT_ERROR (LogLevel.ERROR, R.string.msg_import_error), + MSG_IMPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_import_error_io), MSG_IMPORT_PARTIAL (LogLevel.ERROR, R.string.msg_import_partial), MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success), @@ -622,6 +679,7 @@ public abstract class OperationResult implements Parcelable { MSG_EXPORT_ERROR_STORAGE (LogLevel.ERROR, R.string.msg_export_error_storage), MSG_EXPORT_ERROR_DB (LogLevel.ERROR, R.string.msg_export_error_db), MSG_EXPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_export_error_io), + MSG_EXPORT_ERROR_KEY (LogLevel.ERROR, R.string.msg_export_error_key), MSG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_success), MSG_CRT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_crt_upload_success), @@ -688,7 +746,7 @@ public abstract class OperationResult implements Parcelable { public static class OperationLog implements Iterable<LogEntryParcel> { - private final List<LogEntryParcel> mParcels = new ArrayList<LogEntryParcel>(); + private final List<LogEntryParcel> mParcels = new ArrayList<>(); /// Simple convenience method public void add(LogType type, int indent, Object... parameters) { @@ -713,7 +771,7 @@ public abstract class OperationResult implements Parcelable { } public boolean containsType(LogType type) { - for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) { + for(LogEntryParcel entry : new IterableIterator<>(mParcels.iterator())) { if (entry.mType == type) { return true; } @@ -722,7 +780,7 @@ public abstract class OperationResult implements Parcelable { } public boolean containsWarnings() { - for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) { + for(LogEntryParcel entry : new IterableIterator<>(mParcels.iterator())) { if (entry.mType.mLevel == LogLevel.WARN || entry.mType.mLevel == LogLevel.ERROR) { return true; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java new file mode 100644 index 000000000..611353ac9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.operations.results; + +import android.os.Parcel; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; + +public class PgpEditKeyResult extends OperationResult { + + private transient UncachedKeyRing mRing; + public final long mRingMasterKeyId; + + public PgpEditKeyResult(int result, OperationLog log, + UncachedKeyRing ring) { + super(result, log); + mRing = ring; + mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none; + } + + public UncachedKeyRing getRing() { + return mRing; + } + + public PgpEditKeyResult(Parcel source) { + super(source); + mRingMasterKeyId = source.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeLong(mRingMasterKeyId); + } + + public static Creator<PgpEditKeyResult> CREATOR = new Creator<PgpEditKeyResult>() { + public PgpEditKeyResult createFromParcel(final Parcel source) { + return new PgpEditKeyResult(source); + } + + public PgpEditKeyResult[] newArray(final int size) { + return new PgpEditKeyResult[size]; + } + }; + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java new file mode 100644 index 000000000..de2f64404 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.operations.results; + +import android.os.Parcel; + +import java.util.Date; + +public class PgpSignEncryptResult extends OperationResult { + + // the fourth bit indicates a "data pending" result! (it's also a form of non-success) + public static final int RESULT_PENDING = RESULT_ERROR + 8; + + // fifth to sixth bit in addition indicate specific type of pending + public static final int RESULT_PENDING_PASSPHRASE = RESULT_PENDING + 16; + public static final int RESULT_PENDING_NFC = RESULT_PENDING + 32; + + long mKeyIdPassphraseNeeded; + + long mNfcKeyId; + byte[] mNfcHash; + int mNfcAlgo; + Date mNfcTimestamp; + String mNfcPassphrase; + byte[] mDetachedSignature; + + public long getKeyIdPassphraseNeeded() { + return mKeyIdPassphraseNeeded; + } + + public void setKeyIdPassphraseNeeded(long keyIdPassphraseNeeded) { + mKeyIdPassphraseNeeded = keyIdPassphraseNeeded; + } + + public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, Date nfcTimestamp, String passphrase) { + mNfcKeyId = nfcKeyId; + mNfcHash = nfcHash; + mNfcAlgo = nfcAlgo; + mNfcTimestamp = nfcTimestamp; + mNfcPassphrase = passphrase; + } + + public void setDetachedSignature(byte[] detachedSignature) { + mDetachedSignature = detachedSignature; + } + + public long getNfcKeyId() { + return mNfcKeyId; + } + + public byte[] getNfcHash() { + return mNfcHash; + } + + public int getNfcAlgo() { + return mNfcAlgo; + } + + public Date getNfcTimestamp() { + return mNfcTimestamp; + } + + public String getNfcPassphrase() { + return mNfcPassphrase; + } + + public byte[] getDetachedSignature() { + return mDetachedSignature; + } + + public boolean isPending() { + return (mResult & RESULT_PENDING) == RESULT_PENDING; + } + + public PgpSignEncryptResult(int result, OperationLog log) { + super(result, log); + } + + public PgpSignEncryptResult(Parcel source) { + super(source); + mNfcHash = source.readInt() != 0 ? source.createByteArray() : null; + mNfcAlgo = source.readInt(); + mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null; + mDetachedSignature = source.readInt() != 0 ? source.createByteArray() : null; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + if (mNfcHash != null) { + dest.writeInt(1); + dest.writeByteArray(mNfcHash); + } else { + dest.writeInt(0); + } + dest.writeInt(mNfcAlgo); + if (mNfcTimestamp != null) { + dest.writeInt(1); + dest.writeLong(mNfcTimestamp.getTime()); + } else { + dest.writeInt(0); + } + if (mDetachedSignature != null) { + dest.writeInt(1); + dest.writeByteArray(mDetachedSignature); + } else { + dest.writeInt(0); + } + } + + public static final Creator<PgpSignEncryptResult> CREATOR = new Creator<PgpSignEncryptResult>() { + public PgpSignEncryptResult createFromParcel(final Parcel source) { + return new PgpSignEncryptResult(source); + } + + public PgpSignEncryptResult[] newArray(final int size) { + return new PgpSignEncryptResult[size]; + } + }; + +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java new file mode 100644 index 000000000..af9aff84a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.operations.results; + +import android.os.Parcel; + +public class PromoteKeyResult extends OperationResult { + + public final Long mMasterKeyId; + + public PromoteKeyResult(int result, OperationLog log, Long masterKeyId) { + super(result, log); + mMasterKeyId = masterKeyId; + } + + public PromoteKeyResult(Parcel source) { + super(source); + mMasterKeyId = source.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeLong(mMasterKeyId); + } + + public static Creator<PromoteKeyResult> CREATOR = new Creator<PromoteKeyResult>() { + public PromoteKeyResult createFromParcel(final Parcel source) { + return new PromoteKeyResult(source); + } + + public PromoteKeyResult[] newArray(final int size) { + return new PromoteKeyResult[size]; + } + }; + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java index 57daf3430..ed0de65b0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java @@ -19,74 +19,42 @@ package org.sufficientlysecure.keychain.operations.results; import android.os.Parcel; -import java.util.Date; +import java.util.ArrayList; public class SignEncryptResult extends OperationResult { - // the fourth bit indicates a "data pending" result! (it's also a form of non-success) - public static final int RESULT_PENDING = RESULT_ERROR + 8; - - // fifth to sixth bit in addition indicate specific type of pending - public static final int RESULT_PENDING_PASSPHRASE = RESULT_PENDING + 16; - public static final int RESULT_PENDING_NFC = RESULT_PENDING + 32; - - long mKeyIdPassphraseNeeded; - - long mNfcKeyId; - byte[] mNfcHash; - int mNfcAlgo; - Date mNfcTimestamp; - String mNfcPassphrase; - - public long getKeyIdPassphraseNeeded() { - return mKeyIdPassphraseNeeded; - } - - public void setKeyIdPassphraseNeeded(long keyIdPassphraseNeeded) { - mKeyIdPassphraseNeeded = keyIdPassphraseNeeded; - } - - public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, Date nfcTimestamp, String passphrase) { - mNfcKeyId = nfcKeyId; - mNfcHash = nfcHash; - mNfcAlgo = nfcAlgo; - mNfcTimestamp = nfcTimestamp; - mNfcPassphrase = passphrase; - } - - public long getNfcKeyId() { - return mNfcKeyId; - } + ArrayList<PgpSignEncryptResult> mResults; + byte[] mResultBytes; - public byte[] getNfcHash() { - return mNfcHash; - } - - public int getNfcAlgo() { - return mNfcAlgo; - } - - public Date getNfcTimestamp() { - return mNfcTimestamp; - } + public static final int RESULT_PENDING = RESULT_ERROR + 8; - public String getNfcPassphrase() { - return mNfcPassphrase; + public PgpSignEncryptResult getPending() { + for (PgpSignEncryptResult sub : mResults) { + if (sub.isPending()) { + return sub; + } + } + return null; } - public boolean isPending() { - return (mResult & RESULT_PENDING) == RESULT_PENDING; + public SignEncryptResult(int result, OperationLog log, ArrayList<PgpSignEncryptResult> results) { + super(result, log); + mResults = results; } - public SignEncryptResult(int result, OperationLog log) { + public SignEncryptResult(int result, OperationLog log, ArrayList<PgpSignEncryptResult> results, byte[] resultBytes) { super(result, log); + mResults = results; + mResultBytes = resultBytes; } public SignEncryptResult(Parcel source) { super(source); - mNfcHash = source.readInt() != 0 ? source.createByteArray() : null; - mNfcAlgo = source.readInt(); - mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null; + mResults = source.createTypedArrayList(PgpSignEncryptResult.CREATOR); + } + + public byte[] getResultBytes() { + return mResultBytes; } public int describeContents() { @@ -95,19 +63,7 @@ public class SignEncryptResult extends OperationResult { public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); - if (mNfcHash != null) { - dest.writeInt(1); - dest.writeByteArray(mNfcHash); - } else { - dest.writeInt(0); - } - dest.writeInt(mNfcAlgo); - if (mNfcTimestamp != null) { - dest.writeInt(1); - dest.writeLong(mNfcTimestamp.getTime()); - } else { - dest.writeInt(0); - } + dest.writeTypedList(mResults); } public static final Creator<SignEncryptResult> CREATOR = new Creator<SignEncryptResult>() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SingletonResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SingletonResult.java index 43cc85522..04cb241f9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SingletonResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SingletonResult.java @@ -18,13 +18,8 @@ package org.sufficientlysecure.keychain.operations.results; -import android.app.Activity; import android.os.Parcel; -import com.github.johnpersano.supertoasts.SuperCardToast; -import com.github.johnpersano.supertoasts.SuperToast; -import com.github.johnpersano.supertoasts.util.Style; - /** This is a simple subclass meant to contain only a single log message. This log * message is also shown without a log button in the createNotify SuperToast. */ public class SingletonResult extends OperationResult { @@ -41,35 +36,6 @@ public class SingletonResult extends OperationResult { } @Override - public SuperCardToast createNotify(final Activity activity) { - - // there is exactly one error msg - use that one - String str = activity.getString(mLog.iterator().next().mType.getMsgId()); - int color; - - // Determine color by result type - if (cancelled()) { - color = Style.RED; - } else if (success()) { - if (getLog().containsWarnings()) { - color = Style.ORANGE; - } else { - color = Style.GREEN; - } - } else { - color = Style.RED; - } - - SuperCardToast toast = new SuperCardToast(activity, SuperToast.Type.STANDARD, - Style.getStyle(color, SuperToast.Animations.POPUP)); - toast.setText(str); - toast.setDuration(SuperToast.Duration.EXTRA_LONG); - toast.setIndeterminate(false); - toast.setSwipeToDismiss(true); - return toast; - } - - @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index db0a9b137..4adacaf23 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.openpgp.PGPKeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.IterableIterator; @@ -80,9 +79,8 @@ public abstract class CanonicalizedKeyRing extends KeyRing { public boolean isExpired() { // Is the master key expired? - Date creationDate = getRing().getPublicKey().getCreationTime(); - Date expiryDate = getRing().getPublicKey().getValidSeconds() > 0 - ? new Date(creationDate.getTime() + getRing().getPublicKey().getValidSeconds() * 1000) : null; + Date creationDate = getPublicKey().getCreationTime(); + Date expiryDate = getPublicKey().getExpiryTime(); Date now = new Date(); return creationDate.after(now) || (expiryDate != null && expiryDate.before(now)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java index 3539a4ceb..303070333 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java @@ -20,8 +20,16 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.IterableIterator; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Iterator; /** Wrapper for a PGPPublicKey. * @@ -53,7 +61,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { public boolean canSign() { // if key flags subpacket is available, honor it! - if (getKeyUsage() != null) { + if (getKeyUsage() != 0) { return (getKeyUsage() & KeyFlags.SIGN_DATA) != 0; } @@ -66,7 +74,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { public boolean canCertify() { // if key flags subpacket is available, honor it! - if (getKeyUsage() != null) { + if (getKeyUsage() != 0) { return (getKeyUsage() & KeyFlags.CERTIFY_OTHER) != 0; } @@ -79,7 +87,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { public boolean canEncrypt() { // if key flags subpacket is available, honor it! - if (getKeyUsage() != null) { + if (getKeyUsage() != 0) { return (getKeyUsage() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0; } @@ -93,13 +101,79 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { public boolean canAuthenticate() { // if key flags subpacket is available, honor it! - if (getKeyUsage() != null) { + if (getKeyUsage() != 0) { return (getKeyUsage() & KeyFlags.AUTHENTICATION) != 0; } return false; } + public boolean isRevoked() { + return mPublicKey.getSignaturesOfType(isMasterKey() + ? PGPSignature.KEY_REVOCATION + : PGPSignature.SUBKEY_REVOCATION).hasNext(); + } + + public boolean isExpired () { + Date expiry = getExpiryTime(); + return expiry != null && expiry.before(new Date()); + } + + public long getValidSeconds() { + + long seconds; + + // the getValidSeconds method is unreliable for master keys. we need to iterate all + // user ids, then use the most recent certification from a non-revoked user id + if (isMasterKey()) { + Date latestCreation = null; + seconds = 0; + + for (byte[] rawUserId : getUnorderedRawUserIds()) { + Iterator<WrappedSignature> sigs = getSignaturesForRawId(rawUserId); + + // there is always a certification, so this call is safe + WrappedSignature sig = sigs.next(); + + // we know a user id has at most two sigs: one certification, one revocation. + // if the sig is a revocation, or there is another sig (which is a revocation), + // the data in this uid is not relevant + if (sig.isRevocation() || sigs.hasNext()) { + continue; + } + + // this is our revocation, UNLESS there is a newer certificate! + if (latestCreation == null || latestCreation.before(sig.getCreationTime())) { + latestCreation = sig.getCreationTime(); + seconds = sig.getKeyExpirySeconds(); + } + } + } else { + seconds = mPublicKey.getValidSeconds(); + } + + return seconds; + } + + public Date getExpiryTime() { + long seconds = getValidSeconds(); + + if (seconds > Integer.MAX_VALUE) { + Log.e(Constants.TAG, "error, expiry time too large"); + return null; + } + if (seconds == 0) { + // no expiry + return null; + } + Date creationDate = getCreationTime(); + Calendar calendar = GregorianCalendar.getInstance(); + calendar.setTime(creationDate); + calendar.add(Calendar.SECOND, (int) seconds); + + return calendar.getTime(); + } + /** Same method as superclass, but we make it public. */ public Integer getKeyUsage() { return super.getKeyUsage(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index 85ef3eaa4..c2506685d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -18,9 +18,11 @@ package org.sufficientlysecure.keychain.pgp; +import org.spongycastle.bcpg.S2K; import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.IterableIterator; @@ -76,7 +78,7 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() { @SuppressWarnings("unchecked") final Iterator<PGPPublicKey> it = getRing().getPublicKeys(); - return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() { + return new IterableIterator<>(new Iterator<CanonicalizedPublicKey>() { @Override public boolean hasNext() { return it.hasNext(); @@ -94,4 +96,13 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { }); } + /** Create a dummy secret ring from this key */ + public UncachedKeyRing createDummySecretRing () { + + PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), + S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY); + return new UncachedKeyRing(secRing); + + } + }
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index f9fa41528..40f2f48ad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -36,19 +36,16 @@ import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; -import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFactoryBuilder; import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFactoryBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SignatureException; import java.util.Date; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -83,7 +80,8 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } public enum SecretKeyType { - UNAVAILABLE(0), GNU_DUMMY(1), PASSPHRASE(2), PASSPHRASE_EMPTY(3), DIVERT_TO_CARD(4); + UNAVAILABLE(0), GNU_DUMMY(1), PASSPHRASE(2), PASSPHRASE_EMPTY(3), DIVERT_TO_CARD(4), PIN(5), + PATTERN(6); final int mNum; @@ -101,6 +99,10 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { return PASSPHRASE_EMPTY; case 4: return DIVERT_TO_CARD; + case 5: + return PIN; + case 6: + return PATTERN; // if this case happens, it's probably a check from a database value default: return UNAVAILABLE; @@ -135,6 +137,11 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { // It means the passphrase is empty return SecretKeyType.PASSPHRASE_EMPTY; } catch (PGPException e) { + HashMap<String,String> notation = getRing().getLocalNotationData(); + if (notation.containsKey("unlock.pin@sufficientlysecure.org") + && "1".equals(notation.get("unlock.pin@sufficientlysecure.org"))) { + return SecretKeyType.PIN; + } // Otherwise, it's just a regular ol' passphrase return SecretKeyType.PASSPHRASE; } @@ -175,7 +182,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { * @return */ public LinkedList<Integer> getSupportedHashAlgorithms() { - LinkedList<Integer> supported = new LinkedList<Integer>(); + LinkedList<Integer> supported = new LinkedList<>(); if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { // No support for MD5 @@ -240,7 +247,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { int signatureType; if (cleartext) { - // for sign-only ascii text + // for sign-only ascii text (cleartext signature) signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; } else { signatureType = PGPSignature.BINARY_DOCUMENT; @@ -255,11 +262,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { spGen.setSignatureCreationTime(false, nfcCreationTimestamp); signatureGenerator.setHashedSubpackets(spGen.generate()); return signatureGenerator; - } catch (PgpKeyNotFoundException e) { + } catch (PgpKeyNotFoundException | PGPException e) { // TODO: simply throw PGPException! throw new PgpGeneralException("Error initializing signature!", e); - } catch (PGPException e) { - throw new PgpGeneralException("Error initializing signature!", e); } } @@ -289,6 +294,12 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { throw new PrivateKeyNotUnlockedException(); } + if (!isMasterKey()) { + throw new AssertionError("tried to certify with non-master key, this is a programming error!"); + } + if (publicKeyRing.getMasterKeyId() == getKeyId()) { + throw new AssertionError("key tried to self-certify, this is a programming error!"); + } // create a signatureGenerator from the supplied masterKeyId and passphrase PGPSignatureGenerator signatureGenerator; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java index e20155cc6..b5f6a5b09 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java @@ -18,25 +18,19 @@ package org.sufficientlysecure.keychain.pgp; -import org.spongycastle.bcpg.S2K; -import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPObjectFactory; -import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.PGPSignature; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; -import java.util.HashSet; +import java.util.HashMap; import java.util.Iterator; public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { @@ -92,7 +86,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { public IterableIterator<CanonicalizedSecretKey> secretKeyIterator() { final Iterator<PGPSecretKey> it = mRing.getSecretKeys(); - return new IterableIterator<CanonicalizedSecretKey>(new Iterator<CanonicalizedSecretKey>() { + return new IterableIterator<>(new Iterator<CanonicalizedSecretKey>() { @Override public boolean hasNext() { return it.hasNext(); @@ -112,7 +106,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() { final Iterator<PGPPublicKey> it = getRing().getPublicKeys(); - return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() { + return new IterableIterator<>(new Iterator<CanonicalizedPublicKey>() { @Override public boolean hasNext() { return it.hasNext(); @@ -130,4 +124,16 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { }); } + public HashMap<String,String> getLocalNotationData() { + HashMap<String,String> result = new HashMap<>(); + Iterator<PGPSignature> it = getRing().getPublicKey().getKeySignatures(); + while (it.hasNext()) { + WrappedSignature sig = new WrappedSignature(it.next()); + if (sig.isLocal()) { + result.putAll(sig.getNotation()); + } + } + return result; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index fc5064e79..46defebf7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.pgp; import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.Log; @@ -33,7 +32,7 @@ public class OpenPgpSignatureResultBuilder { // OpenPgpSignatureResult private boolean mSignatureOnly = false; private String mPrimaryUserId; - private ArrayList<String> mUserIds = new ArrayList<String>(); + private ArrayList<String> mUserIds = new ArrayList<>(); private long mKeyId; // builder @@ -84,6 +83,10 @@ public class OpenPgpSignatureResultBuilder { this.mUserIds = userIds; } + public boolean isValidSignature() { + return mValidSignature; + } + public void initValid(CanonicalizedPublicKeyRing signingRing, CanonicalizedPublicKey signingKey) { setSignatureAvailable(true); @@ -101,8 +104,8 @@ public class OpenPgpSignatureResultBuilder { setUserIds(signingRing.getUnorderedUserIds()); // either master key is expired/revoked or this specific subkey is expired/revoked - setKeyExpired(signingRing.isExpired() || signingKey.isExpired()); - setKeyRevoked(signingRing.isRevoked() || signingKey.isRevoked()); + setKeyExpired(signingRing.isExpired() || signingKey.isMaybeExpired()); + setKeyRevoked(signingRing.isRevoked() || signingKey.isMaybeRevoked()); } public OpenPgpSignatureResult build() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 5589a3521..2ba0b6231 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -29,7 +29,6 @@ import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedDataList; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPLiteralData; -import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPOnePassSignature; import org.spongycastle.openpgp.PGPOnePassSignatureList; import org.spongycastle.openpgp.PGPPBEEncryptedData; @@ -37,10 +36,10 @@ import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureList; import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory; import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; @@ -55,6 +54,9 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -85,10 +87,11 @@ public class PgpDecryptVerify extends BaseOperation { private Set<Long> mAllowedKeyIds; private boolean mDecryptMetadataOnly; private byte[] mDecryptedSessionKey; + private byte[] mDetachedSignature; private String mRequiredSignerFingerprint; private boolean mSignedLiteralData; - private PgpDecryptVerify(Builder builder) { + protected PgpDecryptVerify(Builder builder) { super(builder.mContext, builder.mProviderHelper, builder.mProgressable); // private Constructor can only be called from Builder @@ -100,6 +103,7 @@ public class PgpDecryptVerify extends BaseOperation { this.mAllowedKeyIds = builder.mAllowedKeyIds; this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly; this.mDecryptedSessionKey = builder.mDecryptedSessionKey; + this.mDetachedSignature = builder.mDetachedSignature; this.mSignedLiteralData = builder.mSignedLiteralData; this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint; } @@ -109,15 +113,16 @@ public class PgpDecryptVerify extends BaseOperation { private Context mContext; private ProviderHelper mProviderHelper; private InputData mData; - private OutputStream mOutStream; // optional + private OutputStream mOutStream = null; private Progressable mProgressable = null; private boolean mAllowSymmetricDecryption = true; private String mPassphrase = null; private Set<Long> mAllowedKeyIds = null; private boolean mDecryptMetadataOnly = false; private byte[] mDecryptedSessionKey = null; + private byte[] mDetachedSignature = null; private String mRequiredSignerFingerprint = null; private boolean mSignedLiteralData = false; @@ -182,6 +187,14 @@ public class PgpDecryptVerify extends BaseOperation { return this; } + /** + * If detachedSignature != null, it will be used exclusively to verify the signature + */ + public Builder setDetachedSignature(byte[] detachedSignature) { + mDetachedSignature = detachedSignature; + return this; + } + public PgpDecryptVerify build() { return new PgpDecryptVerify(this); } @@ -192,24 +205,32 @@ public class PgpDecryptVerify extends BaseOperation { */ public DecryptVerifyResult execute() { try { - // automatically works with ascii armor input and binary - InputStream in = PGPUtil.getDecoderStream(mData.getInputStream()); - - if (in instanceof ArmoredInputStream) { - ArmoredInputStream aIn = (ArmoredInputStream) in; - // it is ascii armored - Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine()); - - if (mSignedLiteralData) { - return verifySignedLiteralData(aIn, 0); - } else if (aIn.isClearText()) { - // a cleartext signature, verify it with the other method - return verifyCleartextSignature(aIn, 0); + if (mDetachedSignature != null) { + Log.d(Constants.TAG, "Detached signature present, verifying with this signature only"); + + return verifyDetachedSignature(mData.getInputStream(), 0); + } else { + // automatically works with PGP ascii armor and PGP binary + InputStream in = PGPUtil.getDecoderStream(mData.getInputStream()); + + if (in instanceof ArmoredInputStream) { + ArmoredInputStream aIn = (ArmoredInputStream) in; + // it is ascii armored + Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine()); + + if (mSignedLiteralData) { + return verifySignedLiteralData(aIn, 0); + } else if (aIn.isClearText()) { + // a cleartext signature, verify it with the other method + return verifyCleartextSignature(aIn, 0); + } else { + // else: ascii armored encryption! go on... + return decryptVerify(in, 0); + } + } else { + return decryptVerify(in, 0); } - // else: ascii armored encryption! go on... } - - return decryptVerify(in, 0); } catch (PGPException e) { Log.d(Constants.TAG, "PGPException", e); OperationLog log = new OperationLog(); @@ -233,12 +254,12 @@ public class PgpDecryptVerify extends BaseOperation { // thinking that the proof-fetching operation is going to take most of the time updateProgress(R.string.progress_reading_data, 75, 100); - PGPObjectFactory pgpF = new PGPObjectFactory(in, new JcaKeyFingerprintCalculator()); + JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in); Object o = pgpF.nextObject(); if (o instanceof PGPCompressedData) { log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1); - pgpF = new PGPObjectFactory(((PGPCompressedData) o).getDataStream(), new JcaKeyFingerprintCalculator()); + pgpF = new JcaPGPObjectFactory(((PGPCompressedData) o).getDataStream()); o = pgpF.nextObject(); updateProgress(R.string.progress_decompressing_data, 80, 100); } @@ -363,7 +384,7 @@ public class PgpDecryptVerify extends BaseOperation { log.add(LogType.MSG_DC, indent); indent += 1; - PGPObjectFactory pgpF = new PGPObjectFactory(in, new JcaKeyFingerprintCalculator()); + JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in); PGPEncryptedDataList enc; Object o = pgpF.nextObject(); @@ -392,6 +413,25 @@ public class PgpDecryptVerify extends BaseOperation { boolean symmetricPacketFound = false; boolean anyPacketFound = false; + // If the input stream is armored, and there is a charset specified, take a note for later + // https://tools.ietf.org/html/rfc4880#page56 + String charset = null; + if (in instanceof ArmoredInputStream) { + ArmoredInputStream aIn = (ArmoredInputStream) in; + if (aIn.getArmorHeaders() != null) { + for (String header : aIn.getArmorHeaders()) { + String[] pieces = header.split(":", 2); + if (pieces.length == 2 && "charset".equalsIgnoreCase(pieces[0])) { + charset = pieces[1].trim(); + break; + } + } + if (charset != null) { + log.add(LogType.MSG_DC_CHARSET, indent, charset); + } + } + } + // go through all objects and find one we can decrypt while (it.hasNext()) { Object obj = it.next(); @@ -415,19 +455,19 @@ public class PgpDecryptVerify extends BaseOperation { ); } catch (ProviderHelper.NotFoundException e) { // continue with the next packet in the while loop - log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent +1); + log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1); continue; } if (secretKeyRing == null) { // continue with the next packet in the while loop - log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent +1); + log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1); continue; } // get subkey which has been used for this encryption packet secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId); if (secretEncryptionKey == null) { // should actually never happen, so no need to be more specific. - log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent +1); + log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1); continue; } @@ -441,7 +481,7 @@ public class PgpDecryptVerify extends BaseOperation { if (!mAllowedKeyIds.contains(masterKeyId)) { // this key is in our db, but NOT allowed! // continue with the next packet in the while loop - log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent +1); + log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1); continue; } } @@ -456,15 +496,15 @@ public class PgpDecryptVerify extends BaseOperation { try { // returns "" if key has no passphrase mPassphrase = getCachedPassphrase(subKeyId); - log.add(LogType.MSG_DC_PASS_CACHED, indent +1); + log.add(LogType.MSG_DC_PASS_CACHED, indent + 1); } catch (PassphraseCacheInterface.NoSecretKeyException e) { - log.add(LogType.MSG_DC_ERROR_NO_KEY, indent +1); + log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } // if passphrase was not cached, return here indicating that a passphrase is missing! if (mPassphrase == null) { - log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent +1); + log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1); DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE, log); result.setKeyIdPassphraseNeeded(subKeyId); @@ -480,8 +520,8 @@ public class PgpDecryptVerify extends BaseOperation { log.add(LogType.MSG_DC_SYM, indent); - if (! mAllowSymmetricDecryption) { - log.add(LogType.MSG_DC_SYM_SKIP, indent +1); + if (!mAllowSymmetricDecryption) { + log.add(LogType.MSG_DC_SYM_SKIP, indent + 1); continue; } @@ -496,7 +536,7 @@ public class PgpDecryptVerify extends BaseOperation { // if no passphrase is given, return here // indicating that a passphrase is missing! if (mPassphrase == null) { - log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent +1); + log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE, log); } @@ -541,13 +581,13 @@ public class PgpDecryptVerify extends BaseOperation { updateProgress(R.string.progress_extracting_key, currentProgress, 100); try { - log.add(LogType.MSG_DC_UNLOCKING, indent +1); + log.add(LogType.MSG_DC_UNLOCKING, indent + 1); if (!secretEncryptionKey.unlock(mPassphrase)) { - log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent +1); + log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } } catch (PgpGeneralException e) { - log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent +1); + log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } @@ -559,7 +599,7 @@ public class PgpDecryptVerify extends BaseOperation { = secretEncryptionKey.getDecryptorFactory(mDecryptedSessionKey); clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); } catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) { - log.add(LogType.MSG_DC_PENDING_NFC, indent +1); + log.add(LogType.MSG_DC_PENDING_NFC, indent + 1); DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_NFC, log); result.setNfcState(secretEncryptionKey.getKeyId(), e.encryptedSessionKey, mPassphrase); @@ -570,11 +610,11 @@ public class PgpDecryptVerify extends BaseOperation { // If we didn't find any useful data, error out // no packet has been found where we have the corresponding secret key in our db log.add( - anyPacketFound ? LogType.MSG_DC_ERROR_NO_KEY : LogType.MSG_DC_ERROR_NO_DATA, indent +1); + anyPacketFound ? LogType.MSG_DC_ERROR_NO_KEY : LogType.MSG_DC_ERROR_NO_DATA, indent + 1); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } - PGPObjectFactory plainFact = new PGPObjectFactory(clear, new JcaKeyFingerprintCalculator()); + JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear); Object dataChunk = plainFact.nextObject(); OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); int signatureIndex = -1; @@ -585,25 +625,27 @@ public class PgpDecryptVerify extends BaseOperation { indent += 1; if (dataChunk instanceof PGPCompressedData) { - log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent +1); + log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1); currentProgress += 2; updateProgress(R.string.progress_decompressing_data, currentProgress, 100); PGPCompressedData compressedData = (PGPCompressedData) dataChunk; - PGPObjectFactory fact = new PGPObjectFactory(compressedData.getDataStream(), new JcaKeyFingerprintCalculator()); + JcaPGPObjectFactory fact = new JcaPGPObjectFactory(compressedData.getDataStream()); dataChunk = fact.nextObject(); plainFact = fact; } PGPOnePassSignature signature = null; if (dataChunk instanceof PGPOnePassSignatureList) { - log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent +1); + log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent + 1); currentProgress += 2; updateProgress(R.string.progress_processing_signature, currentProgress, 100); PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; + // NOTE: following code is similar to processSignature, but for PGPOnePassSignature + // go through all signatures // and find out for which signature we have a key in our database for (int i = 0; i < sigList.size(); ++i) { @@ -649,19 +691,15 @@ public class PgpDecryptVerify extends BaseOperation { OpenPgpMetadata metadata; if (dataChunk instanceof PGPLiteralData) { - log.add(LogType.MSG_DC_CLEAR_DATA, indent +1); + log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1); indent += 2; currentProgress += 4; updateProgress(R.string.progress_decrypting, currentProgress, 100); PGPLiteralData literalData = (PGPLiteralData) dataChunk; - // TODO: how to get the real original size? - // this is the encrypted size so if we enable compression this value is wrong! - long originalSize = mData.getSize() - mData.getStreamPosition(); - if (originalSize < 0) { - originalSize = 0; - } + // reported size may be null if partial packets are involved (highly unlikely though) + Long originalSize = literalData.getDataLengthIfAvailable(); String originalFilename = literalData.getFileName(); String mimeType = null; @@ -689,18 +727,20 @@ public class PgpDecryptVerify extends BaseOperation { originalFilename, mimeType, literalData.getModificationTime().getTime(), - originalSize); + originalSize == null ? 0 : originalSize); - if ( ! originalFilename.equals("")) { + if (!"".equals(originalFilename)) { log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename); } - log.add(LogType.MSG_DC_CLEAR_META_MIME, indent +1, + log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, mimeType); - log.add(LogType.MSG_DC_CLEAR_META_TIME, indent +1, + log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1, new Date(literalData.getModificationTime().getTime()).toString()); - if (originalSize != 0) { + if (originalSize != null) { log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, Long.toString(originalSize)); + } else { + log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1); } // return here if we want to decrypt the metadata only @@ -708,6 +748,7 @@ public class PgpDecryptVerify extends BaseOperation { log.add(LogType.MSG_DC_OK_META_ONLY, indent); DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); + result.setCharset(charset); result.setDecryptMetadata(metadata); return result; } @@ -730,7 +771,10 @@ public class PgpDecryptVerify extends BaseOperation { int length; byte[] buffer = new byte[1 << 16]; while ((length = dataIn.read(buffer)) > 0) { - mOutStream.write(buffer, 0, length); + Log.d(Constants.TAG, "read bytes: " + length); + if (mOutStream != null) { + mOutStream.write(buffer, 0, length); + } // update signature buffer if signature is also present if (signature != null) { @@ -745,9 +789,8 @@ public class PgpDecryptVerify extends BaseOperation { progress = 100; } progressScaler.setProgress((int) progress, 100); - } else { - // TODO: slow annealing to fake a progress? } + // TODO: slow annealing to fake a progress? } if (signature != null) { @@ -764,9 +807,9 @@ public class PgpDecryptVerify extends BaseOperation { // Verify signature and check binding signatures boolean validSignature = signature.verify(messageSignature); if (validSignature) { - log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent +1); + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1); } else { - log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent +1); + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1); } signatureResultBuilder.setValidSignature(validSignature); } @@ -777,8 +820,6 @@ public class PgpDecryptVerify extends BaseOperation { metadata = null; } - OpenPgpSignatureResult signatureResult = signatureResultBuilder.build(); - if (encryptedData.isIntegrityProtected()) { updateProgress(R.string.progress_verifying_integrity, 95, 100); @@ -792,9 +833,9 @@ public class PgpDecryptVerify extends BaseOperation { // If no valid signature is present: // Handle missing integrity protection like failed integrity protection! // The MDC packet can be stripped by an attacker! - if (signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED - && signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED) { - log.add(LogType.MSG_DC_ERROR_INTEGRITY_CHECK, indent); + Log.d(Constants.TAG, "MDC fail"); + if (!signatureResultBuilder.isValidSignature()) { + log.add(LogType.MSG_DC_ERROR_INTEGRITY_MISSING, indent); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } } @@ -807,7 +848,8 @@ public class PgpDecryptVerify extends BaseOperation { DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); result.setDecryptMetadata(metadata); - result.setSignatureResult(signatureResult); + result.setSignatureResult(signatureResultBuilder.build()); + result.setCharset(charset); return result; } @@ -830,7 +872,7 @@ public class PgpDecryptVerify extends BaseOperation { ByteArrayOutputStream out = new ByteArrayOutputStream(); - updateProgress(R.string.progress_done, 0, 100); + updateProgress(R.string.progress_reading_data, 0, 100); ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); int lookAhead = readInputLine(lineOut, aIn); @@ -850,10 +892,12 @@ public class PgpDecryptVerify extends BaseOperation { out.close(); byte[] clearText = out.toByteArray(); - mOutStream.write(clearText); + if (mOutStream != null) { + mOutStream.write(clearText); + } updateProgress(R.string.progress_processing_signature, 60, 100); - PGPObjectFactory pgpFact = new PGPObjectFactory(aIn, new JcaKeyFingerprintCalculator()); + JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(aIn); PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); if (sigList == null) { @@ -861,45 +905,7 @@ public class PgpDecryptVerify extends BaseOperation { return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } - CanonicalizedPublicKeyRing signingRing = null; - CanonicalizedPublicKey signingKey = null; - int signatureIndex = -1; - - // go through all signatures - // and find out for which signature we have a key in our database - for (int i = 0; i < sigList.size(); ++i) { - try { - long sigKeyId = sigList.get(i).getKeyID(); - signingRing = mProviderHelper.getCanonicalizedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) - ); - signingKey = signingRing.getPublicKey(sigKeyId); - signatureIndex = i; - } catch (ProviderHelper.NotFoundException e) { - Log.d(Constants.TAG, "key not found, trying next signature..."); - } - } - - PGPSignature signature = null; - - if (signingKey != null) { - // key found in our database! - signature = sigList.get(signatureIndex); - - signatureResultBuilder.initValid(signingRing, signingKey); - - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = - new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey()); - } else { - // no key in our database -> return "unknown pub key" status including the first key id - if (!sigList.isEmpty()) { - signatureResultBuilder.setSignatureAvailable(true); - signatureResultBuilder.setKnownKey(false); - signatureResultBuilder.setKeyId(sigList.get(0).getKeyID()); - } - } + PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder); if (signature != null) { try { @@ -947,6 +953,133 @@ public class PgpDecryptVerify extends BaseOperation { return result; } + private DecryptVerifyResult verifyDetachedSignature(InputStream in, int indent) + throws IOException, PGPException { + + OperationLog log = new OperationLog(); + + OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); + // detached signatures are never encrypted + signatureResultBuilder.setSignatureOnly(true); + + updateProgress(R.string.progress_processing_signature, 0, 100); + InputStream detachedSigIn = new ByteArrayInputStream(mDetachedSignature); + detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn); + + JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(detachedSigIn); + + PGPSignatureList sigList; + Object o = pgpFact.nextObject(); + if (o instanceof PGPCompressedData) { + PGPCompressedData c1 = (PGPCompressedData) o; + pgpFact = new JcaPGPObjectFactory(c1.getDataStream()); + sigList = (PGPSignatureList) pgpFact.nextObject(); + } else if (o instanceof PGPSignatureList) { + sigList = (PGPSignatureList) o; + } else { + log.add(LogType.MSG_DC_ERROR_INVALID_SIGLIST, 0); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + + PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder); + + if (signature != null) { + updateProgress(R.string.progress_reading_data, 60, 100); + + ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100); + long alreadyWritten = 0; + long wholeSize = mData.getSize() - mData.getStreamPosition(); + int length; + byte[] buffer = new byte[1 << 16]; + while ((length = in.read(buffer)) > 0) { + if (mOutStream != null) { + mOutStream.write(buffer, 0, length); + } + + // update signature buffer if signature is also present + signature.update(buffer, 0, length); + + alreadyWritten += length; + if (wholeSize > 0) { + long progress = 100 * alreadyWritten / wholeSize; + // stop at 100% for wrong file sizes... + if (progress > 100) { + progress = 100; + } + progressScaler.setProgress((int) progress, 100); + } + // TODO: slow annealing to fake a progress? + } + + updateProgress(R.string.progress_verifying_signature, 90, 100); + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent); + + // these are not cleartext signatures! + signatureResultBuilder.setSignatureOnly(false); + + // Verify signature and check binding signatures + boolean validSignature = signature.verify(); + if (validSignature) { + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1); + } else { + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1); + } + signatureResultBuilder.setValidSignature(validSignature); + } + + updateProgress(R.string.progress_done, 100, 100); + + log.add(LogType.MSG_DC_OK, indent); + + DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); + result.setSignatureResult(signatureResultBuilder.build()); + return result; + } + + private PGPSignature processPGPSignatureList(PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder) throws PGPException { + CanonicalizedPublicKeyRing signingRing = null; + CanonicalizedPublicKey signingKey = null; + int signatureIndex = -1; + + // go through all signatures + // and find out for which signature we have a key in our database + for (int i = 0; i < sigList.size(); ++i) { + try { + long sigKeyId = sigList.get(i).getKeyID(); + signingRing = mProviderHelper.getCanonicalizedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) + ); + signingKey = signingRing.getPublicKey(sigKeyId); + signatureIndex = i; + } catch (ProviderHelper.NotFoundException e) { + Log.d(Constants.TAG, "key not found, trying next signature..."); + } + } + + PGPSignature signature = null; + + if (signingKey != null) { + // key found in our database! + signature = sigList.get(signatureIndex); + + signatureResultBuilder.initValid(signingRing, signingKey); + + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = + new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey()); + } else { + // no key in our database -> return "unknown pub key" status including the first key id + if (!sigList.isEmpty()) { + signatureResultBuilder.setSignatureAvailable(true); + signatureResultBuilder.setKnownKey(false); + signatureResultBuilder.setKeyId(sigList.get(0).getKeyID()); + } + } + + return signature; + } + /** * Mostly taken from ClearSignedFileProcessor in Bouncy Castle */ @@ -968,7 +1101,7 @@ public class PgpDecryptVerify extends BaseOperation { while ((ch = fIn.read()) >= 0) { bOut.write(ch); if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); + lookAhead = readPastEOL(bOut, ch, fIn); break; } } @@ -985,7 +1118,7 @@ public class PgpDecryptVerify extends BaseOperation { do { bOut.write(ch); if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); + lookAhead = readPastEOL(bOut, ch, fIn); break; } } while ((ch = fIn.read()) >= 0); @@ -997,7 +1130,7 @@ public class PgpDecryptVerify extends BaseOperation { return lookAhead; } - private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) + private static int readPastEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) throws IOException { int lookAhead = fIn.read(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java index 0a15fd98f..12de2f637 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -24,8 +24,8 @@ import android.content.pm.PackageManager.NameNotFoundException; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.io.File; import java.io.IOException; 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 6e45fab99..1a251eb79 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -34,6 +34,7 @@ import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureGenerator; import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; @@ -49,9 +50,10 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -133,7 +135,7 @@ public class PgpKeyOperation { public PgpKeyOperation(Progressable progress) { super(); if (progress != null) { - mProgress = new Stack<Progressable>(); + mProgress = new Stack<>(); mProgress.push(progress); } } @@ -286,13 +288,11 @@ public class PgpKeyOperation { // build new key pair return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date()); - } catch(NoSuchProviderException e) { + } catch(NoSuchProviderException | InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } catch(NoSuchAlgorithmException e) { log.add(LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent); return null; - } catch(InvalidAlgorithmParameterException e) { - throw new RuntimeException(e); } catch(PGPException e) { Log.e(Constants.TAG, "internal pgp error", e); log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); @@ -300,7 +300,7 @@ public class PgpKeyOperation { } } - public EditKeyResult createSecretKeyRing(SaveKeyringParcel saveParcel) { + public PgpEditKeyResult createSecretKeyRing(SaveKeyringParcel saveParcel) { OperationLog log = new OperationLog(); int indent = 0; @@ -313,23 +313,23 @@ public class PgpKeyOperation { if (saveParcel.mAddSubKeys.isEmpty()) { log.add(LogType.MSG_CR_ERROR_NO_MASTER, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } if (saveParcel.mAddUserIds.isEmpty()) { log.add(LogType.MSG_CR_ERROR_NO_USER_ID, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } SubkeyAdd add = saveParcel.mAddSubKeys.remove(0); if ((add.mFlags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) { log.add(LogType.MSG_CR_ERROR_NO_CERTIFY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } if (add.mExpiry == null) { log.add(LogType.MSG_CR_ERROR_NULL_EXPIRY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } subProgressPush(10, 30); @@ -338,7 +338,7 @@ public class PgpKeyOperation { // return null if this failed (an error will already have been logged by createKey) if (keyPair == null) { - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } progress(R.string.progress_building_master_key, 40); @@ -365,10 +365,10 @@ public class PgpKeyOperation { } catch (PGPException e) { log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); Log.e(Constants.TAG, "pgp error encoding key", e); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } catch (IOException e) { Log.e(Constants.TAG, "io error encoding key", e); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } } @@ -387,8 +387,11 @@ public class PgpKeyOperation { * with a passphrase fails, the operation will fail with an unlocking error. More specific * handling of errors should be done in UI code! * + * If the passphrase is null, only a restricted subset of operations will be available, + * namely stripping of subkeys and changing the protection mode of dummy keys. + * */ - public EditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel, + public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel, String passphrase) { OperationLog log = new OperationLog(); @@ -413,7 +416,7 @@ public class PgpKeyOperation { // Make sure this is called with a proper SaveKeyringParcel if (saveParcel.mMasterKeyId == null || saveParcel.mMasterKeyId != wsKR.getMasterKeyId()) { log.add(LogType.MSG_MF_ERROR_KEYID, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // We work on bouncycastle object level here @@ -424,21 +427,26 @@ public class PgpKeyOperation { if (saveParcel.mFingerprint == null || !Arrays.equals(saveParcel.mFingerprint, masterSecretKey.getPublicKey().getFingerprint())) { log.add(LogType.MSG_MF_ERROR_FINGERPRINT, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + // If we have no passphrase, only allow restricted operation + if (passphrase == null) { + return internalRestricted(sKR, saveParcel, log); } // read masterKeyFlags, and use the same as before. // since this is the master key, this contains at least CERTIFY_OTHER PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); int masterKeyFlags = readKeyFlags(masterPublicKey) | KeyFlags.CERTIFY_OTHER; - long masterKeyExpiry = masterPublicKey.getValidSeconds() == 0L ? 0L : - masterPublicKey.getCreationTime().getTime() / 1000 + masterPublicKey.getValidSeconds(); + Date expiryTime = wsKR.getPublicKey().getExpiryTime(); + long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L; return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log); } - private EditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey, + private PgpEditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey, int masterKeyFlags, long masterKeyExpiry, SaveKeyringParcel saveParcel, String passphrase, OperationLog log) { @@ -460,7 +468,7 @@ public class PgpKeyOperation { masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor); } catch (PGPException e) { log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } } @@ -469,7 +477,7 @@ public class PgpKeyOperation { // Check if we were cancelled if (checkCancelled()) { log.add(LogType.MSG_OPERATION_CANCELLED, indent); - return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); } { // work on master secret key @@ -477,7 +485,7 @@ public class PgpKeyOperation { PGPPublicKey modifiedPublicKey = masterPublicKey; // 2a. Add certificates for new user ids - subProgressPush(15, 25); + subProgressPush(15, 23); for (int i = 0; i < saveParcel.mAddUserIds.size(); i++) { progress(R.string.progress_modify_adduid, (i - 1) * (100 / saveParcel.mAddUserIds.size())); @@ -486,7 +494,7 @@ public class PgpKeyOperation { if (userId.equals("")) { log.add(LogType.MSG_MF_UID_ERROR_EMPTY, indent + 1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // this operation supersedes all previous binding and revocation certificates, @@ -494,11 +502,11 @@ public class PgpKeyOperation { @SuppressWarnings("unchecked") Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId); if (it != null) { - for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) { + for (PGPSignature cert : new IterableIterator<>(it)) { if (cert.getKeyID() != masterPublicKey.getKeyID()) { // foreign certificate?! error error error log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION || cert.getSignatureType() == PGPSignature.NO_CERTIFICATION @@ -521,8 +529,37 @@ public class PgpKeyOperation { } subProgressPop(); - // 2b. Add revocations for revoked user ids - subProgressPush(25, 40); + // 2b. Add certificates for new user ids + subProgressPush(23, 32); + for (int i = 0; i < saveParcel.mAddUserAttribute.size(); i++) { + + progress(R.string.progress_modify_adduat, (i - 1) * (100 / saveParcel.mAddUserAttribute.size())); + WrappedUserAttribute attribute = saveParcel.mAddUserAttribute.get(i); + + switch (attribute.getType()) { + // the 'none' type must not succeed + case WrappedUserAttribute.UAT_NONE: + log.add(LogType.MSG_MF_UAT_ERROR_EMPTY, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + case WrappedUserAttribute.UAT_IMAGE: + log.add(LogType.MSG_MF_UAT_ADD_IMAGE, indent); + break; + default: + log.add(LogType.MSG_MF_UAT_ADD_UNKNOWN, indent); + break; + } + + PGPUserAttributeSubpacketVector vector = attribute.getVector(); + + // generate and add new certificate + PGPSignature cert = generateUserAttributeSignature(masterPrivateKey, + masterPublicKey, vector); + modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert); + } + subProgressPop(); + + // 2c. Add revocations for revoked user ids + subProgressPush(32, 40); for (int i = 0; i < saveParcel.mRevokeUserIds.size(); i++) { progress(R.string.progress_modify_revokeuid, (i - 1) * (100 / saveParcel.mRevokeUserIds.size())); @@ -540,7 +577,7 @@ public class PgpKeyOperation { } if (!exists) { log.add(LogType.MSG_MF_ERROR_NOEXIST_REVOKE, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // a duplicate revocation will be removed during canonicalization, so no need to @@ -571,7 +608,7 @@ public class PgpKeyOperation { if (cert.getKeyID() != masterPublicKey.getKeyID()) { // foreign certificate?! error error error log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // we know from canonicalization that if there is any revocation here, it // is valid and not superseded by a newer certification. @@ -592,7 +629,7 @@ public class PgpKeyOperation { if (currentCert == null) { // no certificate found?! error error error log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // we definitely should not update certifications of revoked keys, so just leave it. @@ -600,7 +637,7 @@ public class PgpKeyOperation { // revoked user ids cannot be primary! if (userId.equals(saveParcel.mChangePrimaryUserId)) { log.add(LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } continue; } @@ -649,7 +686,7 @@ public class PgpKeyOperation { if (!ok) { log.add(LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } } @@ -665,7 +702,7 @@ public class PgpKeyOperation { // Check if we were cancelled - again if (checkCancelled()) { log.add(LogType.MSG_OPERATION_CANCELLED, indent); - return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); } // 4a. For each subkey change, generate new subkey binding certificate @@ -681,7 +718,28 @@ public class PgpKeyOperation { if (sKey == null) { log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING, indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + if (change.mDummyStrip || change.mDummyDivert != null) { + // IT'S DANGEROUS~ + // no really, it is. this operation irrevocably removes the private key data from the key + if (change.mDummyStrip) { + sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey()); + } else { + // the serial number must be 16 bytes in length + if (change.mDummyDivert.length != 16) { + log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL, + indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + } + sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); + } + + // This doesn't concern us any further + if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) { + continue; } // expiry must not be in the past @@ -689,7 +747,7 @@ public class PgpKeyOperation { new Date(change.mExpiry*1000).before(new Date())) { log.add(LogType.MSG_MF_ERROR_PAST_EXPIRY, indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // if this is the master key, update uid certificates instead @@ -699,7 +757,7 @@ public class PgpKeyOperation { if ((flags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) { log.add(LogType.MSG_MF_ERROR_NO_CERTIFY, indent + 1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } PGPPublicKey pKey = @@ -707,7 +765,7 @@ public class PgpKeyOperation { flags, expiry, indent, log); if (pKey == null) { // error log entry has already been added by updateMasterCertificates itself - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } masterSecretKey = PGPSecretKey.replacePublicKey(sKey, pKey); masterPublicKey = pKey; @@ -762,7 +820,7 @@ public class PgpKeyOperation { if (sKey == null) { log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING, indent+1, KeyFormattingUtils.convertKeyIdToHex(revocation)); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } PGPPublicKey pKey = sKey.getPublicKey(); @@ -774,30 +832,6 @@ public class PgpKeyOperation { } subProgressPop(); - // 4c. For each subkey to be stripped... do so - subProgressPush(65, 70); - for (int i = 0; i < saveParcel.mStripSubKeys.size(); i++) { - - progress(R.string.progress_modify_subkeystrip, (i-1) * (100 / saveParcel.mStripSubKeys.size())); - long strip = saveParcel.mStripSubKeys.get(i); - log.add(LogType.MSG_MF_SUBKEY_STRIP, - indent, KeyFormattingUtils.convertKeyIdToHex(strip)); - - PGPSecretKey sKey = sKR.getSecretKey(strip); - if (sKey == null) { - log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING, - indent+1, KeyFormattingUtils.convertKeyIdToHex(strip)); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); - } - - // IT'S DANGEROUS~ - // no really, it is. this operation irrevocably removes the private key data from the key - sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey()); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); - - } - subProgressPop(); - // 5. Generate and add new subkeys subProgressPush(70, 90); for (int i = 0; i < saveParcel.mAddSubKeys.size(); i++) { @@ -805,7 +839,7 @@ public class PgpKeyOperation { // Check if we were cancelled - again. This operation is expensive so we do it each loop. if (checkCancelled()) { log.add(LogType.MSG_OPERATION_CANCELLED, indent); - return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); } progress(R.string.progress_modify_subkeyadd, (i-1) * (100 / saveParcel.mAddSubKeys.size())); @@ -815,12 +849,12 @@ public class PgpKeyOperation { if (add.mExpiry == null) { log.add(LogType.MSG_MF_ERROR_NULL_EXPIRY, indent +1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } if (add.mExpiry > 0L && new Date(add.mExpiry*1000).before(new Date())) { log.add(LogType.MSG_MF_ERROR_PAST_EXPIRY, indent +1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // generate a new secret key (privkey only for now) @@ -832,7 +866,7 @@ public class PgpKeyOperation { subProgressPop(); if (keyPair == null) { log.add(LogType.MSG_MF_ERROR_PGP, indent +1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // add subkey binding signature (making this a sub rather than master key) @@ -867,67 +901,20 @@ public class PgpKeyOperation { // Check if we were cancelled - again. This operation is expensive so we do it each loop. if (checkCancelled()) { log.add(LogType.MSG_OPERATION_CANCELLED, indent); - return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); } // 6. If requested, change passphrase - if (saveParcel.mNewPassphrase != null) { + if (saveParcel.mNewUnlock != null) { progress(R.string.progress_modify_passphrase, 90); log.add(LogType.MSG_MF_PASSPHRASE, indent); indent += 1; - PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder().build() - .get(SECRET_KEY_ENCRYPTOR_HASH_ALGO); - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); - // Build key encryptor based on new passphrase - PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( - SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - saveParcel.mNewPassphrase.toCharArray()); - - // noinspection unchecked - for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) { - log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent, - KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID())); - - boolean ok = false; - - try { - // try to set new passphrase - sKey = PGPSecretKey.copyWithNewPassword(sKey, keyDecryptor, keyEncryptorNew); - ok = true; - } catch (PGPException e) { - - // if this is the master key, error! - if (sKey.getKeyID() == masterPublicKey.getKeyID()) { - log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); - } - - // being in here means decrypt failed, likely due to a bad passphrase try - // again with an empty passphrase, maybe we can salvage this - try { - log.add(LogType.MSG_MF_PASSPHRASE_EMPTY_RETRY, indent+1); - PBESecretKeyDecryptor emptyDecryptor = - new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray()); - sKey = PGPSecretKey.copyWithNewPassword(sKey, emptyDecryptor, keyEncryptorNew); - ok = true; - } catch (PGPException e2) { - // non-fatal but not ok, handled below - } - } - - if (!ok) { - // for a subkey, it's merely a warning - log.add(LogType.MSG_MF_PASSPHRASE_FAIL, indent+1, - KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID())); - continue; - } - - sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); - + sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey, + passphrase, saveParcel.mNewUnlock, log, indent); + if (sKR == null) { + // The error has been logged above, just return a bad state + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } indent -= 1; @@ -936,20 +923,235 @@ public class PgpKeyOperation { } catch (IOException e) { Log.e(Constants.TAG, "encountered IOException while modifying key", e); log.add(LogType.MSG_MF_ERROR_ENCODE, indent+1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } catch (PGPException e) { Log.e(Constants.TAG, "encountered pgp error while modifying key", e); log.add(LogType.MSG_MF_ERROR_PGP, indent+1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } catch (SignatureException e) { Log.e(Constants.TAG, "encountered SignatureException while modifying key", e); log.add(LogType.MSG_MF_ERROR_SIG, indent+1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } progress(R.string.progress_done, 100); log.add(LogType.MSG_MF_SUCCESS, indent); - return new EditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); + return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); + + } + + /** This method does the actual modifications in a keyring just like internal, except it + * supports only the subset of operations which require no passphrase, and will error + * otherwise. + */ + private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel, + OperationLog log) { + + int indent = 1; + + progress(R.string.progress_modify, 0); + + // Make sure the saveParcel includes only operations available without passphrae! + if (!saveParcel.isRestrictedOnly()) { + log.add(LogType.MSG_MF_ERROR_RESTRICTED, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + // Check if we were cancelled + if (checkCancelled()) { + log.add(LogType.MSG_OPERATION_CANCELLED, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); + } + + // The only operation we can do here: + // 4a. Strip secret keys, or change their protection mode (stripped/divert-to-card) + subProgressPush(50, 60); + for (int i = 0; i < saveParcel.mChangeSubKeys.size(); i++) { + + progress(R.string.progress_modify_subkeychange, (i - 1) * (100 / saveParcel.mChangeSubKeys.size())); + SaveKeyringParcel.SubkeyChange change = saveParcel.mChangeSubKeys.get(i); + log.add(LogType.MSG_MF_SUBKEY_CHANGE, + indent, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); + + PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId); + if (sKey == null) { + log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING, + indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + if (change.mDummyStrip || change.mDummyDivert != null) { + // IT'S DANGEROUS~ + // no really, it is. this operation irrevocably removes the private key data from the key + if (change.mDummyStrip) { + sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey()); + } else { + // the serial number must be 16 bytes in length + if (change.mDummyDivert.length != 16) { + log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL, + indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert); + } + sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); + } + + } + + // And we're done! + progress(R.string.progress_done, 100); + log.add(LogType.MSG_MF_SUCCESS, indent); + return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); + + } + + + private static PGPSecretKeyRing applyNewUnlock( + PGPSecretKeyRing sKR, + PGPPublicKey masterPublicKey, + PGPPrivateKey masterPrivateKey, + String passphrase, + ChangeUnlockParcel newUnlock, + OperationLog log, int indent) throws PGPException { + + if (newUnlock.mNewPassphrase != null) { + sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPassphrase, log, indent); + + // if there is any old packet with notation data + if (hasNotationData(sKR)) { + + log.add(LogType.MSG_MF_NOTATION_EMPTY, indent); + + // add packet with EMPTY notation data (updates old one, but will be stripped later) + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + { // set subpackets + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + hashedPacketsGen.setExportable(false, false); + sGen.setHashedSubpackets(hashedPacketsGen.generate()); + } + sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); + PGPSignature emptySig = sGen.generateCertification(masterPublicKey); + + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, + PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); + } + + return sKR; + } + + if (newUnlock.mNewPin != null) { + sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPin, log, indent); + + log.add(LogType.MSG_MF_NOTATION_PIN, indent); + + // add packet with "pin" notation data + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + { // set subpackets + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + hashedPacketsGen.setExportable(false, false); + hashedPacketsGen.setNotationData(false, true, "unlock.pin@sufficientlysecure.org", "1"); + sGen.setHashedSubpackets(hashedPacketsGen.generate()); + } + sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); + PGPSignature emptySig = sGen.generateCertification(masterPublicKey); + + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, + PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); + + return sKR; + } + + throw new UnsupportedOperationException("PIN passphrases not yet implemented!"); + + } + + /** This method returns true iff the provided keyring has a local direct key signature + * with notation data. + */ + private static boolean hasNotationData(PGPSecretKeyRing sKR) { + // noinspection unchecked + Iterator<PGPSignature> sigs = sKR.getPublicKey().getKeySignatures(); + while (sigs.hasNext()) { + WrappedSignature sig = new WrappedSignature(sigs.next()); + if (sig.getSignatureType() == PGPSignature.DIRECT_KEY + && sig.isLocal() && !sig.getNotation().isEmpty()) { + return true; + } + } + return false; + } + + private static PGPSecretKeyRing applyNewPassphrase( + PGPSecretKeyRing sKR, + PGPPublicKey masterPublicKey, + String passphrase, + String newPassphrase, + OperationLog log, int indent) throws PGPException { + + PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder().build() + .get(SECRET_KEY_ENCRYPTOR_HASH_ALGO); + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + // Build key encryptor based on new passphrase + PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( + SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + newPassphrase.toCharArray()); + + // noinspection unchecked + for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) { + log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent, + KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID())); + + boolean ok = false; + + try { + // try to set new passphrase + sKey = PGPSecretKey.copyWithNewPassword(sKey, keyDecryptor, keyEncryptorNew); + ok = true; + } catch (PGPException e) { + + // if this is the master key, error! + if (sKey.getKeyID() == masterPublicKey.getKeyID()) { + log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1); + return null; + } + + // being in here means decrypt failed, likely due to a bad passphrase try + // again with an empty passphrase, maybe we can salvage this + try { + log.add(LogType.MSG_MF_PASSPHRASE_EMPTY_RETRY, indent+1); + PBESecretKeyDecryptor emptyDecryptor = + new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray()); + sKey = PGPSecretKey.copyWithNewPassword(sKey, emptyDecryptor, keyEncryptorNew); + ok = true; + } catch (PGPException e2) { + // non-fatal but not ok, handled below + } + } + + if (!ok) { + // for a subkey, it's merely a warning + log.add(LogType.MSG_MF_PASSPHRASE_FAIL, indent+1, + KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID())); + continue; + } + + sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); + + } + + return sKR; } @@ -1072,6 +1274,26 @@ public class PgpKeyOperation { return sGen.generateCertification(userId, pKey); } + private static PGPSignature generateUserAttributeSignature( + PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, + PGPUserAttributeSubpacketVector vector) + throws IOException, PGPException, SignatureException { + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + { + /* critical subpackets: we consider those important for a modern pgp implementation */ + hashedPacketsGen.setSignatureCreationTime(true, new Date()); + } + + sGen.setHashedSubpackets(hashedPacketsGen.generate()); + sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); + return sGen.generateCertification(vector, pKey); + } + private static PGPSignature generateRevocationSignature( PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId) throws IOException, PGPException, SignatureException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java new file mode 100644 index 000000000..9318be006 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java @@ -0,0 +1,176 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.sufficientlysecure.keychain.Constants; + +import java.util.Date; + +public class PgpSignEncryptInput { + + protected String mVersionHeader = null; + protected boolean mEnableAsciiArmorOutput = false; + protected int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED; + protected long[] mEncryptionMasterKeyIds = null; + protected String mSymmetricPassphrase = null; + protected int mSymmetricEncryptionAlgorithm = 0; + protected long mSignatureMasterKeyId = Constants.key.none; + protected Long mSignatureSubKeyId = null; + protected int mSignatureHashAlgorithm = 0; + protected String mSignaturePassphrase = null; + protected long mAdditionalEncryptId = Constants.key.none; + protected byte[] mNfcSignedHash = null; + protected Date mNfcCreationTimestamp = null; + protected boolean mFailOnMissingEncryptionKeyIds = false; + protected String mCharset; + protected boolean mCleartextSignature; + protected boolean mDetachedSignature; + + public String getCharset() { + return mCharset; + } + + public void setCharset(String mCharset) { + this.mCharset = mCharset; + } + + public boolean ismFailOnMissingEncryptionKeyIds() { + return mFailOnMissingEncryptionKeyIds; + } + + public Date getNfcCreationTimestamp() { + return mNfcCreationTimestamp; + } + + public byte[] getNfcSignedHash() { + return mNfcSignedHash; + } + + public long getAdditionalEncryptId() { + return mAdditionalEncryptId; + } + + public PgpSignEncryptInput setAdditionalEncryptId(long additionalEncryptId) { + mAdditionalEncryptId = additionalEncryptId; + return this; + } + + public String getSignaturePassphrase() { + return mSignaturePassphrase; + } + + public PgpSignEncryptInput setSignaturePassphrase(String signaturePassphrase) { + mSignaturePassphrase = signaturePassphrase; + return this; + } + + public int getSignatureHashAlgorithm() { + return mSignatureHashAlgorithm; + } + + public PgpSignEncryptInput setSignatureHashAlgorithm(int signatureHashAlgorithm) { + mSignatureHashAlgorithm = signatureHashAlgorithm; + return this; + } + + public Long getSignatureSubKeyId() { + return mSignatureSubKeyId; + } + + public PgpSignEncryptInput setSignatureSubKeyId(long signatureSubKeyId) { + mSignatureSubKeyId = signatureSubKeyId; + return this; + } + + public long getSignatureMasterKeyId() { + return mSignatureMasterKeyId; + } + + public PgpSignEncryptInput setSignatureMasterKeyId(long signatureMasterKeyId) { + mSignatureMasterKeyId = signatureMasterKeyId; + return this; + } + + public int getSymmetricEncryptionAlgorithm() { + return mSymmetricEncryptionAlgorithm; + } + + public PgpSignEncryptInput setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { + mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; + return this; + } + + public String getSymmetricPassphrase() { + return mSymmetricPassphrase; + } + + public PgpSignEncryptInput setSymmetricPassphrase(String symmetricPassphrase) { + mSymmetricPassphrase = symmetricPassphrase; + return this; + } + + public long[] getEncryptionMasterKeyIds() { + return mEncryptionMasterKeyIds; + } + + public PgpSignEncryptInput setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { + mEncryptionMasterKeyIds = encryptionMasterKeyIds; + return this; + } + + public int getCompressionId() { + return mCompressionId; + } + + public PgpSignEncryptInput setCompressionId(int compressionId) { + mCompressionId = compressionId; + return this; + } + + public boolean ismEnableAsciiArmorOutput() { + return mEnableAsciiArmorOutput; + } + + public String getVersionHeader() { + return mVersionHeader; + } + + public PgpSignEncryptInput setVersionHeader(String versionHeader) { + mVersionHeader = versionHeader; + return this; + } + + public PgpSignEncryptInput setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) { + mEnableAsciiArmorOutput = enableAsciiArmorOutput; + return this; + } + + public PgpSignEncryptInput setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) { + mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds; + return this; + } + + public PgpSignEncryptInput setNfcState(byte[] signedHash, Date creationTimestamp) { + mNfcSignedHash = signedHash; + mNfcCreationTimestamp = creationTimestamp; + return this; + } + + public PgpSignEncryptInput setCleartextSignature(boolean cleartextSignature) { + this.mCleartextSignature = cleartextSignature; + return this; + } + + public boolean isCleartextSignature() { + return mCleartextSignature; + } + + public PgpSignEncryptInput setDetachedSignature(boolean detachedSignature) { + this.mDetachedSignature = detachedSignature; + return this; + } + + public boolean isDetachedSignature() { + return mDetachedSignature; + } +} + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index f89027a19..2fa01d241 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -25,6 +25,7 @@ import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.BCPGOutputStream; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.openpgp.PGPCompressedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedDataGenerator; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPLiteralData; @@ -37,19 +38,20 @@ import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.BaseOperation; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -59,31 +61,23 @@ import java.security.SignatureException; import java.util.Arrays; import java.util.Date; import java.util.LinkedList; +import java.util.concurrent.atomic.AtomicBoolean; -/** - * This class uses a Builder pattern! +/** This class supports a single, low-level, sign/encrypt operation. + * + * The operation of this class takes an Input- and OutputStream plus a + * PgpSignEncryptInput, and signs and/or encrypts the stream as + * parametrized in the PgpSignEncryptInput object. It returns its status + * and a possible detached signature as a SignEncryptResult. + * + * For a high-level operation based on URIs, see SignEncryptOperation. + * + * @see org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput + * @see org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult + * @see org.sufficientlysecure.keychain.operations.SignEncryptOperation + * */ -public class PgpSignEncrypt extends BaseOperation { - private String mVersionHeader; - private InputData mData; - private OutputStream mOutStream; - - private boolean mEnableAsciiArmorOutput; - private int mCompressionId; - private long[] mEncryptionMasterKeyIds; - private String mSymmetricPassphrase; - private int mSymmetricEncryptionAlgorithm; - private long mSignatureMasterKeyId; - private Long mSignatureSubKeyId; - private int mSignatureHashAlgorithm; - private String mSignaturePassphrase; - private long mAdditionalEncryptId; - private boolean mCleartextInput; - private String mOriginalFilename; - private boolean mFailOnMissingEncryptionKeyIds; - - private byte[] mNfcSignedHash = null; - private Date mNfcCreationTimestamp = null; +public class PgpSignEncryptOperation extends BaseOperation { private static byte[] NEW_LINE; @@ -95,241 +89,107 @@ public class PgpSignEncrypt extends BaseOperation { } } - private PgpSignEncrypt(Builder builder) { - super(builder.mContext, builder.mProviderHelper, builder.mProgressable); - - // private Constructor can only be called from Builder - this.mVersionHeader = builder.mVersionHeader; - this.mData = builder.mData; - this.mOutStream = builder.mOutStream; - - this.mEnableAsciiArmorOutput = builder.mEnableAsciiArmorOutput; - this.mCompressionId = builder.mCompressionId; - this.mEncryptionMasterKeyIds = builder.mEncryptionMasterKeyIds; - this.mSymmetricPassphrase = builder.mSymmetricPassphrase; - this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm; - this.mSignatureMasterKeyId = builder.mSignatureMasterKeyId; - this.mSignatureSubKeyId = builder.mSignatureSubKeyId; - this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm; - this.mSignaturePassphrase = builder.mSignaturePassphrase; - this.mAdditionalEncryptId = builder.mAdditionalEncryptId; - this.mCleartextInput = builder.mCleartextInput; - this.mNfcSignedHash = builder.mNfcSignedHash; - this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp; - this.mOriginalFilename = builder.mOriginalFilename; - this.mFailOnMissingEncryptionKeyIds = builder.mFailOnMissingEncryptionKeyIds; + public PgpSignEncryptOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean cancelled) { + super(context, providerHelper, progressable, cancelled); } - public static class Builder { - // mandatory parameter - private Context mContext; - private ProviderHelper mProviderHelper; - private Progressable mProgressable; - private InputData mData; - private OutputStream mOutStream; - - // optional - private String mVersionHeader = null; - private boolean mEnableAsciiArmorOutput = false; - private int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED; - private long[] mEncryptionMasterKeyIds = null; - private String mSymmetricPassphrase = null; - private int mSymmetricEncryptionAlgorithm = 0; - private long mSignatureMasterKeyId = Constants.key.none; - private Long mSignatureSubKeyId = null; - private int mSignatureHashAlgorithm = 0; - private String mSignaturePassphrase = null; - private long mAdditionalEncryptId = Constants.key.none; - private boolean mCleartextInput = false; - private String mOriginalFilename = ""; - private byte[] mNfcSignedHash = null; - private Date mNfcCreationTimestamp = null; - private boolean mFailOnMissingEncryptionKeyIds = false; - - public Builder(Context context, ProviderHelper providerHelper, Progressable progressable, - InputData data, OutputStream outStream) { - mContext = context; - mProviderHelper = providerHelper; - mProgressable = progressable; - - mData = data; - mOutStream = outStream; - } - - public Builder setVersionHeader(String versionHeader) { - mVersionHeader = versionHeader; - return this; - } - - public Builder setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) { - mEnableAsciiArmorOutput = enableAsciiArmorOutput; - return this; - } - - public Builder setCompressionId(int compressionId) { - mCompressionId = compressionId; - return this; - } - - public Builder setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { - mEncryptionMasterKeyIds = encryptionMasterKeyIds; - return this; - } - - public Builder setSymmetricPassphrase(String symmetricPassphrase) { - mSymmetricPassphrase = symmetricPassphrase; - return this; - } - - public Builder setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { - mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; - return this; - } - - public Builder setSignatureMasterKeyId(long signatureMasterKeyId) { - mSignatureMasterKeyId = signatureMasterKeyId; - return this; - } - - public Builder setSignatureSubKeyId(long signatureSubKeyId) { - mSignatureSubKeyId = signatureSubKeyId; - return this; - } - - public Builder setSignatureHashAlgorithm(int signatureHashAlgorithm) { - mSignatureHashAlgorithm = signatureHashAlgorithm; - return this; - } - - public Builder setSignaturePassphrase(String signaturePassphrase) { - mSignaturePassphrase = signaturePassphrase; - return this; - } - - public Builder setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) { - mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds; - return this; - } - - /** - * Also encrypt with the signing keyring - * - * @param additionalEncryptId - * @return - */ - public Builder setAdditionalEncryptId(long additionalEncryptId) { - mAdditionalEncryptId = additionalEncryptId; - return this; - } - - /** - * TODO: test this option! - * - * @param cleartextInput - * @return - */ - public Builder setCleartextInput(boolean cleartextInput) { - mCleartextInput = cleartextInput; - return this; - } - - public Builder setOriginalFilename(String originalFilename) { - mOriginalFilename = originalFilename; - return this; - } - - public Builder setNfcState(byte[] signedHash, Date creationTimestamp) { - mNfcSignedHash = signedHash; - mNfcCreationTimestamp = creationTimestamp; - return this; - } - - public PgpSignEncrypt build() { - return new PgpSignEncrypt(this); - } + public PgpSignEncryptOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { + super(context, providerHelper, progressable); } /** * Signs and/or encrypts data based on parameters of class */ - public SignEncryptResult execute() { + public PgpSignEncryptResult execute(PgpSignEncryptInput input, + InputData inputData, OutputStream outputStream) { int indent = 0; OperationLog log = new OperationLog(); - log.add(LogType.MSG_SE, indent); + log.add(LogType.MSG_PSE, indent); indent += 1; - boolean enableSignature = mSignatureMasterKeyId != Constants.key.none; - boolean enableEncryption = ((mEncryptionMasterKeyIds != null && mEncryptionMasterKeyIds.length > 0) - || mSymmetricPassphrase != null); - boolean enableCompression = (mCompressionId != CompressionAlgorithmTags.UNCOMPRESSED); + boolean enableSignature = input.getSignatureMasterKeyId() != Constants.key.none; + boolean enableEncryption = ((input.getEncryptionMasterKeyIds() != null && input.getEncryptionMasterKeyIds().length > 0) + || input.getSymmetricPassphrase() != null); + boolean enableCompression = (input.getCompressionId() != CompressionAlgorithmTags.UNCOMPRESSED); Log.d(Constants.TAG, "enableSignature:" + enableSignature + "\nenableEncryption:" + enableEncryption + "\nenableCompression:" + enableCompression - + "\nenableAsciiArmorOutput:" + mEnableAsciiArmorOutput); + + "\nenableAsciiArmorOutput:" + input.ismEnableAsciiArmorOutput()); // add additional key id to encryption ids (mostly to do self-encryption) - if (enableEncryption && mAdditionalEncryptId != Constants.key.none) { - mEncryptionMasterKeyIds = Arrays.copyOf(mEncryptionMasterKeyIds, mEncryptionMasterKeyIds.length + 1); - mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mAdditionalEncryptId; + if (enableEncryption && input.getAdditionalEncryptId() != Constants.key.none) { + input.setEncryptionMasterKeyIds(Arrays.copyOf(input.getEncryptionMasterKeyIds(), input.getEncryptionMasterKeyIds().length + 1)); + input.getEncryptionMasterKeyIds()[input.getEncryptionMasterKeyIds().length - 1] = input.getAdditionalEncryptId(); } ArmoredOutputStream armorOut = null; OutputStream out; - if (mEnableAsciiArmorOutput) { - armorOut = new ArmoredOutputStream(mOutStream); - if (mVersionHeader != null) { - armorOut.setHeader("Version", mVersionHeader); + if (input.ismEnableAsciiArmorOutput()) { + armorOut = new ArmoredOutputStream(outputStream); + if (input.getVersionHeader() != null) { + armorOut.setHeader("Version", input.getVersionHeader()); + } + // if we have a charset, put it in the header + if (input.getCharset() != null) { + armorOut.setHeader("Charset", input.getCharset()); } out = armorOut; } else { - out = mOutStream; + out = outputStream; } /* Get keys for signature generation for later usage */ CanonicalizedSecretKey signingKey = null; - long signKeyId; if (enableSignature) { try { // fetch the indicated master key id (the one whose name we sign in) CanonicalizedSecretKeyRing signingKeyRing = - mProviderHelper.getCanonicalizedSecretKeyRing(mSignatureMasterKeyId); + mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId()); + + long signKeyId; + // use specified signing subkey, or find the one to use + if (input.getSignatureSubKeyId() == null) { + signKeyId = signingKeyRing.getSecretSignId(); + } else { + signKeyId = input.getSignatureSubKeyId(); + } + // fetch the specific subkey to sign with, or just use the master key if none specified - signKeyId = mSignatureSubKeyId != null ? mSignatureSubKeyId : mSignatureMasterKeyId; signingKey = signingKeyRing.getSecretKey(signKeyId); - // make sure it's a signing key alright! - } catch (ProviderHelper.NotFoundException e) { - log.add(LogType.MSG_SE_ERROR_SIGN_KEY, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + + } catch (ProviderHelper.NotFoundException | PgpGeneralException e) { + log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } // Make sure we are allowed to sign here! if (!signingKey.canSign()) { - log.add(LogType.MSG_SE_ERROR_KEY_SIGN, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } // if no passphrase was explicitly set try to get it from the cache service - if (mSignaturePassphrase == null) { + if (input.getSignaturePassphrase() == null) { try { // returns "" if key has no passphrase - mSignaturePassphrase = getCachedPassphrase(signKeyId); + input.setSignaturePassphrase(getCachedPassphrase(signingKey.getKeyId())); // TODO // log.add(LogType.MSG_DC_PASS_CACHED, indent + 1); } catch (PassphraseCacheInterface.NoSecretKeyException e) { // TODO // log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } // if passphrase was not cached, return here indicating that a passphrase is missing! - if (mSignaturePassphrase == null) { - log.add(LogType.MSG_SE_PENDING_PASSPHRASE, indent + 1); - SignEncryptResult result = new SignEncryptResult(SignEncryptResult.RESULT_PENDING_PASSPHRASE, log); - result.setKeyIdPassphraseNeeded(signKeyId); + if (input.getSignaturePassphrase() == null) { + log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1); + PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE, log); + result.setKeyIdPassphraseNeeded(signingKey.getKeyId()); return result; } } @@ -337,20 +197,24 @@ public class PgpSignEncrypt extends BaseOperation { updateProgress(R.string.progress_extracting_signature_key, 0, 100); try { - if (!signingKey.unlock(mSignaturePassphrase)) { - log.add(LogType.MSG_SE_ERROR_BAD_PASSPHRASE, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + if (!signingKey.unlock(input.getSignaturePassphrase())) { + log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } catch (PgpGeneralException e) { - log.add(LogType.MSG_SE_ERROR_UNLOCK, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } // check if hash algo is supported + int requestedAlgorithm = input.getSignatureHashAlgorithm(); LinkedList<Integer> supported = signingKey.getSupportedHashAlgorithms(); - if (!supported.contains(mSignatureHashAlgorithm)) { + if (requestedAlgorithm == 0) { // get most preferred - mSignatureHashAlgorithm = supported.getLast(); + input.setSignatureHashAlgorithm(supported.getLast()); + } else if (!supported.contains(requestedAlgorithm)) { + log.add(LogType.MSG_PSE_ERROR_HASH_ALGO, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } updateProgress(R.string.progress_preparing_streams, 2, 100); @@ -358,44 +222,48 @@ public class PgpSignEncrypt extends BaseOperation { /* Initialize PGPEncryptedDataGenerator for later usage */ PGPEncryptedDataGenerator cPk = null; if (enableEncryption) { + int algo = input.getSymmetricEncryptionAlgorithm(); + if (algo == 0) { + algo = PGPEncryptedData.AES_128; + } // has Integrity packet enabled! JcePGPDataEncryptorBuilder encryptorBuilder = - new JcePGPDataEncryptorBuilder(mSymmetricEncryptionAlgorithm) + new JcePGPDataEncryptorBuilder(algo) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME) .setWithIntegrityPacket(true); cPk = new PGPEncryptedDataGenerator(encryptorBuilder); - if (mSymmetricPassphrase != null) { + if (input.getSymmetricPassphrase() != null) { // Symmetric encryption - log.add(LogType.MSG_SE_SYMMETRIC, indent); + log.add(LogType.MSG_PSE_SYMMETRIC, indent); JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = - new JcePBEKeyEncryptionMethodGenerator(mSymmetricPassphrase.toCharArray()); + new JcePBEKeyEncryptionMethodGenerator(input.getSymmetricPassphrase().toCharArray()); cPk.addMethod(symmetricEncryptionGenerator); } else { - log.add(LogType.MSG_SE_ASYMMETRIC, indent); + log.add(LogType.MSG_PSE_ASYMMETRIC, indent); // Asymmetric encryption - for (long id : mEncryptionMasterKeyIds) { + for (long id : input.getEncryptionMasterKeyIds()) { try { CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing( KeyRings.buildUnifiedKeyRingUri(id)); CanonicalizedPublicKey key = keyRing.getEncryptionSubKey(); cPk.addMethod(key.getPubKeyEncryptionGenerator()); - log.add(LogType.MSG_SE_KEY_OK, indent + 1, + log.add(LogType.MSG_PSE_KEY_OK, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id)); } catch (PgpKeyNotFoundException e) { - log.add(LogType.MSG_SE_KEY_WARN, indent + 1, + log.add(LogType.MSG_PSE_KEY_WARN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id)); - if (mFailOnMissingEncryptionKeyIds) { - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + if (input.ismFailOnMissingEncryptionKeyIds()) { + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } catch (ProviderHelper.NotFoundException e) { - log.add(LogType.MSG_SE_KEY_UNKNOWN, indent + 1, + log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id)); - if (mFailOnMissingEncryptionKeyIds) { - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + if (input.ismFailOnMissingEncryptionKeyIds()) { + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } } @@ -408,12 +276,12 @@ public class PgpSignEncrypt extends BaseOperation { updateProgress(R.string.progress_preparing_signature, 4, 100); try { - boolean cleartext = mCleartextInput && mEnableAsciiArmorOutput && !enableEncryption; + boolean cleartext = input.isCleartextSignature() && input.ismEnableAsciiArmorOutput() && !enableEncryption; signatureGenerator = signingKey.getSignatureGenerator( - mSignatureHashAlgorithm, cleartext, mNfcSignedHash, mNfcCreationTimestamp); + input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp()); } catch (PgpGeneralException e) { - log.add(LogType.MSG_SE_ERROR_NFC, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_NFC, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } @@ -424,14 +292,18 @@ public class PgpSignEncrypt extends BaseOperation { OutputStream encryptionOut = null; BCPGOutputStream bcpgOut; + ByteArrayOutputStream detachedByteOut = null; + ArmoredOutputStream detachedArmorOut = null; + BCPGOutputStream detachedBcpgOut = null; + try { if (enableEncryption) { /* actual encryption */ updateProgress(R.string.progress_encrypting, 8, 100); log.add(enableSignature - ? LogType.MSG_SE_SIGCRYPTING - : LogType.MSG_SE_ENCRYPTING, + ? LogType.MSG_PSE_SIGCRYPTING + : LogType.MSG_PSE_ENCRYPTING, indent ); indent += 1; @@ -439,8 +311,8 @@ public class PgpSignEncrypt extends BaseOperation { encryptionOut = cPk.open(out, new byte[1 << 16]); if (enableCompression) { - log.add(LogType.MSG_SE_COMPRESSING, indent); - compressGen = new PGPCompressedDataGenerator(mCompressionId); + log.add(LogType.MSG_PSE_COMPRESSING, indent); + compressGen = new PGPCompressedDataGenerator(input.getCompressionId()); bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut)); } else { bcpgOut = new BCPGOutputStream(encryptionOut); @@ -452,18 +324,18 @@ public class PgpSignEncrypt extends BaseOperation { PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); char literalDataFormatTag; - if (mCleartextInput) { + if (input.isCleartextSignature()) { literalDataFormatTag = PGPLiteralData.UTF8; } else { literalDataFormatTag = PGPLiteralData.BINARY; } - pOut = literalGen.open(bcpgOut, literalDataFormatTag, mOriginalFilename, new Date(), - new byte[1 << 16]); + pOut = literalGen.open(bcpgOut, literalDataFormatTag, + inputData.getOriginalFilename(), new Date(), new byte[1 << 16]); long alreadyWritten = 0; int length; byte[] buffer = new byte[1 << 16]; - InputStream in = mData.getInputStream(); + InputStream in = inputData.getInputStream(); while ((length = in.read(buffer)) > 0) { pOut.write(buffer, 0, length); @@ -473,8 +345,8 @@ public class PgpSignEncrypt extends BaseOperation { } alreadyWritten += length; - if (mData.getSize() > 0) { - long progress = 100 * alreadyWritten / mData.getSize(); + if (inputData.getSize() > 0) { + long progress = 100 * alreadyWritten / inputData.getSize(); progressScaler.setProgress((int) progress, 100); } } @@ -482,16 +354,16 @@ public class PgpSignEncrypt extends BaseOperation { literalGen.close(); indent -= 1; - } else if (enableSignature && mCleartextInput && mEnableAsciiArmorOutput) { + } else if (enableSignature && input.isCleartextSignature() && input.ismEnableAsciiArmorOutput()) { /* cleartext signature: sign-only of ascii text */ updateProgress(R.string.progress_signing, 8, 100); - log.add(LogType.MSG_SE_SIGNING, indent); + log.add(LogType.MSG_PSE_SIGNING_CLEARTEXT, indent); // write -----BEGIN PGP SIGNED MESSAGE----- - armorOut.beginClearText(mSignatureHashAlgorithm); + armorOut.beginClearText(input.getSignatureHashAlgorithm()); - InputStream in = mData.getInputStream(); + InputStream in = inputData.getInputStream(); final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); // update signature buffer with first line @@ -517,16 +389,53 @@ public class PgpSignEncrypt extends BaseOperation { armorOut.endClearText(); pOut = new BCPGOutputStream(armorOut); - } else if (enableSignature && !mCleartextInput) { + } else if (enableSignature && input.isDetachedSignature()) { + /* detached signature */ + + updateProgress(R.string.progress_signing, 8, 100); + log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent); + + InputStream in = inputData.getInputStream(); + + // handle output stream separately for detached signatures + detachedByteOut = new ByteArrayOutputStream(); + OutputStream detachedOut = detachedByteOut; + if (input.ismEnableAsciiArmorOutput()) { + detachedArmorOut = new ArmoredOutputStream(detachedOut); + if (input.getVersionHeader() != null) { + detachedArmorOut.setHeader("Version", input.getVersionHeader()); + } + + detachedOut = detachedArmorOut; + } + detachedBcpgOut = new BCPGOutputStream(detachedOut); + + long alreadyWritten = 0; + int length; + byte[] buffer = new byte[1 << 16]; + while ((length = in.read(buffer)) > 0) { + // no output stream is written, no changed to original data! + + signatureGenerator.update(buffer, 0, length); + + alreadyWritten += length; + if (inputData.getSize() > 0) { + long progress = 100 * alreadyWritten / inputData.getSize(); + progressScaler.setProgress((int) progress, 100); + } + } + + pOut = null; + } else if (enableSignature && !input.isCleartextSignature() && !input.isDetachedSignature()) { /* sign-only binary (files/data stream) */ updateProgress(R.string.progress_signing, 8, 100); - log.add(LogType.MSG_SE_ENCRYPTING, indent); + log.add(LogType.MSG_PSE_SIGNING, indent); - InputStream in = mData.getInputStream(); + InputStream in = inputData.getInputStream(); if (enableCompression) { - compressGen = new PGPCompressedDataGenerator(mCompressionId); + compressGen = new PGPCompressedDataGenerator(input.getCompressionId()); bcpgOut = new BCPGOutputStream(compressGen.open(out)); } else { bcpgOut = new BCPGOutputStream(out); @@ -535,7 +444,8 @@ public class PgpSignEncrypt extends BaseOperation { signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); - pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, mOriginalFilename, new Date(), + pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, + inputData.getOriginalFilename(), new Date(), new byte[1 << 16]); long alreadyWritten = 0; @@ -547,8 +457,8 @@ public class PgpSignEncrypt extends BaseOperation { signatureGenerator.update(buffer, 0, length); alreadyWritten += length; - if (mData.getSize() > 0) { - long progress = 100 * alreadyWritten / mData.getSize(); + if (inputData.getSize() > 0) { + long progress = 100 * alreadyWritten / inputData.getSize(); progressScaler.setProgress((int) progress, 100); } } @@ -556,61 +466,89 @@ public class PgpSignEncrypt extends BaseOperation { literalGen.close(); } else { pOut = null; - log.add(LogType.MSG_SE_CLEARSIGN_ONLY, indent); + // TODO: Is this log right? + log.add(LogType.MSG_PSE_CLEARSIGN_ONLY, indent); } if (enableSignature) { updateProgress(R.string.progress_generating_signature, 95, 100); try { - signatureGenerator.generate().encode(pOut); + if (detachedBcpgOut != null) { + signatureGenerator.generate().encode(detachedBcpgOut); + } else { + signatureGenerator.generate().encode(pOut); + } } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) { // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed - log.add(LogType.MSG_SE_PENDING_NFC, indent); - SignEncryptResult result = - new SignEncryptResult(SignEncryptResult.RESULT_PENDING_NFC, log); + log.add(LogType.MSG_PSE_PENDING_NFC, indent); + PgpSignEncryptResult result = + new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_NFC, log); // Note that the checked key here is the master key, not the signing key // (although these are always the same on Yubikeys) - result.setNfcData(mSignatureSubKeyId, e.hashToSign, e.hashAlgo, e.creationTimestamp, mSignaturePassphrase); - Log.d(Constants.TAG, "e.hashToSign"+ Hex.toHexString(e.hashToSign)); + result.setNfcData(input.getSignatureSubKeyId(), e.hashToSign, e.hashAlgo, e.creationTimestamp, input.getSignaturePassphrase()); + Log.d(Constants.TAG, "e.hashToSign" + Hex.toHexString(e.hashToSign)); return result; } } // closing outputs // NOTE: closing needs to be done in the correct order! - // TODO: closing bcpgOut and pOut??? - if (enableEncryption) { - if (enableCompression) { + if (encryptionOut != null) { + if (compressGen != null) { compressGen.close(); } encryptionOut.close(); } - if (mEnableAsciiArmorOutput) { + // Note: Closing ArmoredOutputStream does not close the underlying stream + if (armorOut != null) { armorOut.close(); } - - out.close(); - mOutStream.close(); + // Note: Closing ArmoredOutputStream does not close the underlying stream + if (detachedArmorOut != null) { + detachedArmorOut.close(); + } + // Also closes detachedBcpgOut + if (detachedByteOut != null) { + detachedByteOut.close(); + } + if (out != null) { + out.close(); + } + if (outputStream != null) { + outputStream.close(); + } } catch (SignatureException e) { - log.add(LogType.MSG_SE_ERROR_SIG, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_SIG, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } catch (PGPException e) { - log.add(LogType.MSG_SE_ERROR_PGP, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_PGP, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } catch (IOException e) { - log.add(LogType.MSG_SE_ERROR_IO, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_IO, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } updateProgress(R.string.progress_done, 100, 100); - log.add(LogType.MSG_SE_OK, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_OK, log); - + log.add(LogType.MSG_PSE_OK, indent); + PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_OK, log); + if (detachedByteOut != null) { + try { + detachedByteOut.flush(); + detachedByteOut.close(); + } catch (IOException e) { + // silently catch + } + result.setDetachedSignature(detachedByteOut.toByteArray()); + } + return result; } + /** + * Remove whitespaces on line endings + */ private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, final PGPSignatureGenerator pSignatureGenerator) throws IOException, SignatureException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java new file mode 100644 index 000000000..a4ed33397 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java @@ -0,0 +1,135 @@ +package org.sufficientlysecure.keychain.pgp; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** This parcel stores the input of one or more PgpSignEncrypt operations. + * All operations will use the same general paramters, differing only in + * input and output. Each input/output set depends on the paramters: + * + * - Each input uri is individually encrypted/signed + * - If a byte array is supplied, it is treated as an input before uris are processed + * - The number of output uris must match the number of input uris, plus one more + * if there is a byte array present. + * - Once the output uris are empty, there must be exactly one input (uri xor bytes) + * left, which will be returned in a byte array as part of the result parcel. + * + */ +public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable { + + public ArrayList<Uri> mInputUris = new ArrayList<>(); + public ArrayList<Uri> mOutputUris = new ArrayList<>(); + public byte[] mBytes; + + public SignEncryptParcel() { + super(); + } + + public SignEncryptParcel(Parcel src) { + + // we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable + mVersionHeader = src.readString(); + mEnableAsciiArmorOutput = src.readInt() == 1; + mCompressionId = src.readInt(); + mEncryptionMasterKeyIds = src.createLongArray(); + mSymmetricPassphrase = src.readString(); + mSymmetricEncryptionAlgorithm = src.readInt(); + mSignatureMasterKeyId = src.readLong(); + mSignatureSubKeyId = src.readInt() == 1 ? src.readLong() : null; + mSignatureHashAlgorithm = src.readInt(); + mSignaturePassphrase = src.readString(); + mAdditionalEncryptId = src.readLong(); + mNfcSignedHash = src.createByteArray(); + mNfcCreationTimestamp = src.readInt() == 1 ? new Date(src.readLong()) : null; + mFailOnMissingEncryptionKeyIds = src.readInt() == 1; + mCharset = src.readString(); + mCleartextSignature = src.readInt() == 1; + mDetachedSignature = src.readInt() == 1; + + mInputUris = src.createTypedArrayList(Uri.CREATOR); + mOutputUris = src.createTypedArrayList(Uri.CREATOR); + mBytes = src.createByteArray(); + + } + + public byte[] getBytes() { + return mBytes; + } + + public void setBytes(byte[] bytes) { + mBytes = bytes; + } + + public List<Uri> getInputUris() { + return Collections.unmodifiableList(mInputUris); + } + + public void addInputUris(Collection<Uri> inputUris) { + mInputUris.addAll(inputUris); + } + + public List<Uri> getOutputUris() { + return Collections.unmodifiableList(mOutputUris); + } + + public void addOutputUris(ArrayList<Uri> outputUris) { + mOutputUris.addAll(outputUris); + } + + @Override + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mVersionHeader); + dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0); + dest.writeInt(mCompressionId); + dest.writeLongArray(mEncryptionMasterKeyIds); + dest.writeString(mSymmetricPassphrase); + dest.writeInt(mSymmetricEncryptionAlgorithm); + dest.writeLong(mSignatureMasterKeyId); + if (mSignatureSubKeyId != null) { + dest.writeInt(1); + dest.writeLong(mSignatureSubKeyId); + } else { + dest.writeInt(0); + } + dest.writeInt(mSignatureHashAlgorithm); + dest.writeString(mSignaturePassphrase); + dest.writeLong(mAdditionalEncryptId); + dest.writeByteArray(mNfcSignedHash); + if (mNfcCreationTimestamp != null) { + dest.writeInt(1); + dest.writeLong(mNfcCreationTimestamp.getTime()); + } else { + dest.writeInt(0); + } + dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0); + dest.writeString(mCharset); + dest.writeInt(mCleartextSignature ? 1 : 0); + dest.writeInt(mDetachedSignature ? 1 : 0); + + dest.writeTypedList(mInputUris); + dest.writeTypedList(mOutputUris); + dest.writeByteArray(mBytes); + } + + public static final Creator<SignEncryptParcel> CREATOR = new Creator<SignEncryptParcel>() { + public SignEncryptParcel createFromParcel(final Parcel source) { + return new SignEncryptParcel(source); + } + + public SignEncryptParcel[] newArray(final int size) { + return new SignEncryptParcel[size]; + } + }; + +} 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 d05ce3d5c..681aff56d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.bcpg.UserAttributeSubpacketTags; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPObjectFactory; @@ -30,18 +31,18 @@ import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Utf8Util; -import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -49,11 +50,13 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.TimeZone; import java.util.TreeSet; /** Wrapper around PGPKeyRing class, to be constructed from bytes. @@ -136,7 +139,7 @@ public class UncachedKeyRing { public static UncachedKeyRing decodeFromData(byte[] data) throws PgpGeneralException, IOException { - Iterator<UncachedKeyRing> parsed = fromStream(new ByteArrayInputStream(data)); + IteratorWithIOThrow<UncachedKeyRing> parsed = fromStream(new ByteArrayInputStream(data)); if ( ! parsed.hasNext()) { throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); @@ -152,14 +155,14 @@ public class UncachedKeyRing { } - public static Iterator<UncachedKeyRing> fromStream(final InputStream stream) throws IOException { + public static IteratorWithIOThrow<UncachedKeyRing> fromStream(final InputStream stream) { - return new Iterator<UncachedKeyRing>() { + return new IteratorWithIOThrow<UncachedKeyRing>() { UncachedKeyRing mNext = null; PGPObjectFactory mObjectFactory = null; - private void cacheNext() { + private void cacheNext() throws IOException { if (mNext != null) { return; } @@ -188,21 +191,19 @@ public class UncachedKeyRing { // if we are past the while loop, that means the objectFactory had no next mObjectFactory = null; } - } catch (IOException e) { - Log.e(Constants.TAG, "IOException while processing stream. ArmoredInputStream CRC check failed?", e); } catch (ArrayIndexOutOfBoundsException e) { - Log.e(Constants.TAG, "ArmoredInputStream decode failed, symbol is not in decodingTable!", e); + throw new IOException(e); } } @Override - public boolean hasNext() { + public boolean hasNext() throws IOException { cacheNext(); return mNext != null; } @Override - public UncachedKeyRing next() { + public UncachedKeyRing next() throws IOException { try { cacheNext(); return mNext; @@ -210,15 +211,15 @@ public class UncachedKeyRing { mNext = null; } } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } }; } + public interface IteratorWithIOThrow<E> { + public boolean hasNext() throws IOException; + public E next() throws IOException; + } + public void encodeArmored(OutputStream out, String version) throws IOException { ArmoredOutputStream aos = new ArmoredOutputStream(out); if (version != null) { @@ -265,6 +266,35 @@ public class UncachedKeyRing { */ @SuppressWarnings("ConstantConditions") public CanonicalizedKeyRing canonicalize(OperationLog log, int indent) { + return canonicalize(log, indent, false); + } + + + /** "Canonicalizes" a public key, removing inconsistencies in the process. + * + * 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 in other positions if not of known 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 an OperationResultParcel. + * + * @param forExport if this is true, non-exportable signatures will be removed + * @return A canonicalized key, or null on fatal error (log will include a message in this case) + * + */ + @SuppressWarnings("ConstantConditions") + public CanonicalizedKeyRing canonicalize(OperationLog log, int indent, boolean forExport) { log.add(isSecret() ? LogType.MSG_KC_SECRET : LogType.MSG_KC_PUBLIC, indent, KeyFormattingUtils.convertKeyIdToHex(getMasterKeyId())); @@ -276,7 +306,10 @@ public class UncachedKeyRing { return null; } - final Date now = new Date(); + Calendar nowCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + // allow for diverging clocks up to one day when checking creation time + nowCal.add(Calendar.DAY_OF_YEAR, 1); + final Date nowPlusOneDay = nowCal.getTime(); int redundantCerts = 0, badCerts = 0; @@ -297,6 +330,7 @@ public class UncachedKeyRing { PGPPublicKey modified = masterKey; PGPSignature revocation = null; + PGPSignature notation = null; for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getKeySignatures())) { int type = zert.getSignatureType(); @@ -306,47 +340,80 @@ public class UncachedKeyRing { || type == PGPSignature.CASUAL_CERTIFICATION || type == PGPSignature.POSITIVE_CERTIFICATION || type == PGPSignature.CERTIFICATION_REVOCATION) { - log.add(LogType.MSG_KC_REVOKE_BAD_TYPE_UID, indent); + log.add(LogType.MSG_KC_MASTER_BAD_TYPE_UID, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } WrappedSignature cert = new WrappedSignature(zert); - if (type != PGPSignature.KEY_REVOCATION) { + if (type != PGPSignature.KEY_REVOCATION && type != PGPSignature.DIRECT_KEY) { // Unknown type, just remove - log.add(LogType.MSG_KC_REVOKE_BAD_TYPE, indent, "0x" + Integer.toString(type, 16)); + log.add(LogType.MSG_KC_MASTER_BAD_TYPE, indent, "0x" + Integer.toString(type, 16)); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } - if (cert.getCreationTime().after(now)) { + if (cert.getCreationTime().after(nowPlusOneDay)) { // Creation date in the future? No way! - log.add(LogType.MSG_KC_REVOKE_BAD_TIME, indent); + log.add(LogType.MSG_KC_MASTER_BAD_TIME, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } - if (cert.isLocal()) { - // Remove revocation certs with "local" flag - log.add(LogType.MSG_KC_REVOKE_BAD_LOCAL, indent); + try { + cert.init(masterKey); + if (!cert.verifySignature(masterKey)) { + log.add(LogType.MSG_KC_MASTER_BAD, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; + } + } catch (PgpGeneralException e) { + log.add(LogType.MSG_KC_MASTER_BAD_ERR, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } - try { - cert.init(masterKey); - if (!cert.verifySignature(masterKey)) { - log.add(LogType.MSG_KC_REVOKE_BAD, indent); + // if this is for export, we always remove any non-exportable certs + if (forExport && cert.isLocal()) { + // Remove revocation certs with "local" flag + log.add(LogType.MSG_KC_MASTER_LOCAL, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + continue; + } + + // special case: non-exportable, direct key signatures for notations! + if (cert.getSignatureType() == PGPSignature.DIRECT_KEY) { + // must be local, otherwise strip! + if (!cert.isLocal()) { + log.add(LogType.MSG_KC_MASTER_BAD_TYPE, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } - } catch (PgpGeneralException e) { - log.add(LogType.MSG_KC_REVOKE_BAD_ERR, indent); + + // first notation? fine then. + if (notation == null) { + notation = zert; + // more notations? at least one is superfluous, then. + } else if (notation.getCreationTime().before(zert.getCreationTime())) { + log.add(LogType.MSG_KC_NOTATION_DUP, indent); + modified = PGPPublicKey.removeCertification(modified, notation); + redundantCerts += 1; + notation = zert; + } else { + log.add(LogType.MSG_KC_NOTATION_DUP, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + redundantCerts += 1; + } + continue; + } else if (cert.isLocal()) { + // Remove revocation certs with "local" flag + log.add(LogType.MSG_KC_MASTER_BAD_LOCAL, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -368,7 +435,17 @@ public class UncachedKeyRing { } } - ArrayList<String> processedUserIds = new ArrayList<String>(); + // If we have a notation packet, check if there is even any data in it? + if (notation != null) { + // If there isn't, might as well strip it + if (new WrappedSignature(notation).getNotation().isEmpty()) { + log.add(LogType.MSG_KC_NOTATION_EMPTY, indent); + modified = PGPPublicKey.removeCertification(modified, notation); + redundantCerts += 1; + } + } + + ArrayList<String> processedUserIds = new ArrayList<>(); for (byte[] rawUserId : new IterableIterator<byte[]>(masterKey.getRawUserIDs())) { String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); @@ -393,7 +470,7 @@ public class UncachedKeyRing { @SuppressWarnings("unchecked") Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForID(rawUserId); if (signaturesIt != null) { - for (PGPSignature zert : new IterableIterator<PGPSignature>(signaturesIt)) { + for (PGPSignature zert : new IterableIterator<>(signaturesIt)) { WrappedSignature cert = new WrappedSignature(zert); long certId = cert.getKeyId(); @@ -410,7 +487,7 @@ public class UncachedKeyRing { continue; } - if (cert.getCreationTime().after(now)) { + if (cert.getCreationTime().after(nowPlusOneDay)) { // Creation date in the future? No way! log.add(LogType.MSG_KC_UID_BAD_TIME, indent); modified = PGPPublicKey.removeCertification(modified, rawUserId, zert); @@ -530,6 +607,170 @@ public class UncachedKeyRing { return null; } + ArrayList<PGPUserAttributeSubpacketVector> processedUserAttributes = new ArrayList<>(); + for (PGPUserAttributeSubpacketVector userAttribute : + new IterableIterator<PGPUserAttributeSubpacketVector>(masterKey.getUserAttributes())) { + + if (userAttribute.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE) != null) { + log.add(LogType.MSG_KC_UAT_JPEG, indent); + } else { + log.add(LogType.MSG_KC_UAT_UNKNOWN, indent); + } + + try { + indent += 1; + + // check for duplicate user attributes + if (processedUserAttributes.contains(userAttribute)) { + log.add(LogType.MSG_KC_UAT_DUP, indent); + // strip out the first found user id with this name + modified = PGPPublicKey.removeCertification(modified, userAttribute); + } + processedUserAttributes.add(userAttribute); + + PGPSignature selfCert = null; + revocation = null; + + // look through signatures for this specific user id + @SuppressWarnings("unchecked") + Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForUserAttribute(userAttribute); + if (signaturesIt != null) { + for (PGPSignature zert : new IterableIterator<>(signaturesIt)) { + 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(LogType.MSG_KC_UAT_BAD_TYPE, + indent, "0x" + Integer.toString(zert.getSignatureType(), 16)); + modified = PGPPublicKey.removeCertification(modified, userAttribute, zert); + badCerts += 1; + continue; + } + + if (cert.getCreationTime().after(nowPlusOneDay)) { + // Creation date in the future? No way! + log.add(LogType.MSG_KC_UAT_BAD_TIME, indent); + modified = PGPPublicKey.removeCertification(modified, userAttribute, zert); + badCerts += 1; + continue; + } + + if (cert.isLocal()) { + // Creation date in the future? No way! + log.add(LogType.MSG_KC_UAT_BAD_LOCAL, indent); + modified = PGPPublicKey.removeCertification(modified, userAttribute, 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(LogType.MSG_KC_UAT_FOREIGN, + indent, KeyFormattingUtils.convertKeyIdToHex(certId)); + modified = PGPPublicKey.removeCertification(modified, userAttribute, zert); + badCerts += 1; + } + continue; + } + + // Otherwise, first make sure it checks out + try { + cert.init(masterKey); + if (!cert.verifySignature(masterKey, userAttribute)) { + log.add(LogType.MSG_KC_UAT_BAD, + indent); + modified = PGPPublicKey.removeCertification(modified, userAttribute, zert); + badCerts += 1; + continue; + } + } catch (PgpGeneralException e) { + log.add(LogType.MSG_KC_UAT_BAD_ERR, + indent); + modified = PGPPublicKey.removeCertification(modified, userAttribute, 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())) { + log.add(LogType.MSG_KC_UAT_CERT_DUP, + indent); + modified = PGPPublicKey.removeCertification(modified, userAttribute, selfCert); + redundantCerts += 1; + selfCert = zert; + } else { + log.add(LogType.MSG_KC_UAT_CERT_DUP, + indent); + modified = PGPPublicKey.removeCertification(modified, userAttribute, zert); + redundantCerts += 1; + } + // If there is a revocation certificate, and it's older than this, drop it + if (revocation != null + && revocation.getCreationTime().before(selfCert.getCreationTime())) { + log.add(LogType.MSG_KC_UAT_REVOKE_OLD, + indent); + modified = PGPPublicKey.removeCertification(modified, userAttribute, revocation); + revocation = null; + redundantCerts += 1; + } + break; + + case PGPSignature.CERTIFICATION_REVOCATION: + // If this is older than the (latest) self cert, drop it + if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) { + log.add(LogType.MSG_KC_UAT_REVOKE_OLD, + indent); + modified = PGPPublicKey.removeCertification(modified, userAttribute, zert); + redundantCerts += 1; + 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())) { + log.add(LogType.MSG_KC_UAT_REVOKE_DUP, + indent); + modified = PGPPublicKey.removeCertification(modified, userAttribute, revocation); + redundantCerts += 1; + revocation = zert; + } else { + log.add(LogType.MSG_KC_UAT_REVOKE_DUP, + indent); + modified = PGPPublicKey.removeCertification(modified, userAttribute, zert); + redundantCerts += 1; + } + break; + } + } + } + + // If no valid certificate (if only a revocation) remains, drop it + if (selfCert == null && revocation == null) { + log.add(LogType.MSG_KC_UAT_REMOVE, + indent); + modified = PGPPublicKey.removeCertification(modified, userAttribute); + } + + } finally { + indent -= 1; + } + } + + // Replace modified key in the keyring ring = replacePublicKey(ring, modified); indent -= 1; @@ -537,7 +778,7 @@ public class UncachedKeyRing { } // Keep track of ids we encountered so far - Set<Long> knownIds = new HashSet<Long>(); + Set<Long> knownIds = new HashSet<>(); // Process all keys for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(ring.getPublicKeys())) { @@ -592,7 +833,7 @@ public class UncachedKeyRing { continue; } - if (cert.getCreationTime().after(now)) { + if (cert.getCreationTime().after(nowPlusOneDay)) { // Creation date in the future? No way! log.add(LogType.MSG_KC_SUB_BAD_TIME, indent); badCerts += 1; @@ -777,8 +1018,8 @@ public class UncachedKeyRing { /** 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. + * The combined keyring contains the subkeys, user ids and user attributes 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 @@ -801,7 +1042,7 @@ public class UncachedKeyRing { } // remember which certs we already added. this is cheaper than semantic deduplication - Set<byte[]> certs = new TreeSet<byte[]>(new Comparator<byte[]>() { + Set<byte[]> certs = new TreeSet<>(new Comparator<byte[]>() { public int compare(byte[] left, byte[] right) { // check for length equality if (left.length != right.length) { @@ -883,7 +1124,7 @@ public class UncachedKeyRing { if (signaturesIt == null) { continue; } - for (PGPSignature cert : new IterableIterator<PGPSignature>(signaturesIt)) { + for (PGPSignature cert : new IterableIterator<>(signaturesIt)) { // Don't merge foreign stuff into secret keys if (cert.getKeyID() != masterKeyId && isSecret()) { continue; @@ -898,6 +1139,32 @@ public class UncachedKeyRing { modified = PGPPublicKey.addCertification(modified, rawUserId, cert); } } + + // Copy over all user attribute certificates + for (PGPUserAttributeSubpacketVector vector : + new IterableIterator<PGPUserAttributeSubpacketVector>(key.getUserAttributes())) { + @SuppressWarnings("unchecked") + Iterator<PGPSignature> signaturesIt = key.getSignaturesForUserAttribute(vector); + // no signatures for this user attribute attribute, skip it + if (signaturesIt == null) { + continue; + } + for (PGPSignature cert : new IterableIterator<>(signaturesIt)) { + // 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, vector, cert); + } + } + // If anything changed, save the updated (sub)key if (modified != resultKey) { result = replacePublicKey(result, modified); 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 c4cacaca7..9276cba10 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -20,10 +20,10 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.ECPublicBCPGKey; import org.spongycastle.bcpg.SignatureSubpacketTags; -import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureSubpacketVector; +import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.IterableIterator; @@ -50,7 +50,7 @@ public class UncachedPublicKey { } /** The revocation signature is NOT checked here, so this may be false! */ - public boolean isRevoked() { + public boolean isMaybeRevoked() { return mPublicKey.getSignaturesOfType(isMasterKey() ? PGPSignature.KEY_REVOCATION : PGPSignature.SUBKEY_REVOCATION).hasNext(); @@ -60,25 +60,8 @@ public class UncachedPublicKey { return mPublicKey.getCreationTime(); } - public Date getExpiryTime() { - long seconds = mPublicKey.getValidSeconds(); - if (seconds > Integer.MAX_VALUE) { - Log.e(Constants.TAG, "error, expiry time too large"); - return null; - } - if (seconds == 0) { - // no expiry - return null; - } - Date creationDate = getCreationTime(); - Calendar calendar = GregorianCalendar.getInstance(); - calendar.setTime(creationDate); - calendar.add(Calendar.SECOND, (int) seconds); - - return calendar.getTime(); - } - - public boolean isExpired() { + /** The revocation signature is NOT checked here, so this may be false! */ + public boolean isMaybeExpired() { Date creationDate = mPublicKey.getCreationTime(); Date expiryDate = mPublicKey.getValidSeconds() > 0 ? new Date(creationDate.getTime() + mPublicKey.getValidSeconds() * 1000) : null; @@ -135,7 +118,7 @@ public class UncachedPublicKey { continue; } - for (PGPSignature sig : new IterableIterator<PGPSignature>(signaturesIt)) { + for (PGPSignature sig : new IterableIterator<>(signaturesIt)) { try { // if this is a revocation, this is not the user id @@ -186,18 +169,20 @@ public class UncachedPublicKey { } /** - * Returns primary user id if existing. If not, return first encountered user id. + * Returns primary user id if existing. If not, return first encountered user id. If there + * is no user id, return null (this can only happen for not yet canonicalized keys during import) */ public String getPrimaryUserIdWithFallback() { String userId = getPrimaryUserId(); if (userId == null) { - userId = (String) mPublicKey.getUserIDs().next(); + Iterator<String> it = mPublicKey.getUserIDs(); + userId = it.hasNext() ? it.next() : null; } return userId; } public ArrayList<String> getUnorderedUserIds() { - ArrayList<String> userIds = new ArrayList<String>(); + ArrayList<String> userIds = new ArrayList<>(); for (byte[] rawUserId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) { // use our decoding method userIds.add(Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId)); @@ -206,13 +191,22 @@ public class UncachedPublicKey { } public ArrayList<byte[]> getUnorderedRawUserIds() { - ArrayList<byte[]> userIds = new ArrayList<byte[]>(); + ArrayList<byte[]> userIds = new ArrayList<>(); for (byte[] userId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) { userIds.add(userId); } return userIds; } + public ArrayList<WrappedUserAttribute> getUnorderedUserAttributes() { + ArrayList<WrappedUserAttribute> userAttributes = new ArrayList<>(); + for (PGPUserAttributeSubpacketVector userAttribute : + new IterableIterator<PGPUserAttributeSubpacketVector>(mPublicKey.getUserAttributes())) { + userAttributes.add(new WrappedUserAttribute(userAttribute)); + } + return userAttributes; + } + public boolean isElGamalEncrypt() { return getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT; } @@ -268,33 +262,103 @@ public class UncachedPublicKey { } } + public Iterator<WrappedSignature> getSignaturesForUserAttribute(WrappedUserAttribute attribute) { + final Iterator<PGPSignature> it = mPublicKey.getSignaturesForUserAttribute(attribute.getVector()); + if (it != null) { + return new Iterator<WrappedSignature>() { + public void remove() { + it.remove(); + } + public WrappedSignature next() { + return new WrappedSignature(it.next()); + } + public boolean hasNext() { + return it.hasNext(); + } + }; + } else { + return null; + } + } + /** Get all key usage flags. * If at least one key flag subpacket is present return these. If no * subpacket is present it returns null. * * Note that this method has package visiblity because it is used in test * cases. Certificates of UncachedPublicKey instances can NOT be assumed to - * be verified, so the result of this method should not be used in other - * places! + * be verified or even by the correct key, so the result of this method + * should never be used in other places! */ @SuppressWarnings("unchecked") Integer getKeyUsage() { if (mCacheUsage == null) { + PGPSignature mostRecentSig = null; for (PGPSignature sig : new IterableIterator<PGPSignature>(mPublicKey.getSignatures())) { if (mPublicKey.isMasterKey() && sig.getKeyID() != mPublicKey.getKeyID()) { continue; } - PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + switch (sig.getSignatureType()) { + case PGPSignature.DEFAULT_CERTIFICATION: + case PGPSignature.POSITIVE_CERTIFICATION: + case PGPSignature.CASUAL_CERTIFICATION: + case PGPSignature.NO_CERTIFICATION: + case PGPSignature.SUBKEY_BINDING: + break; + // if this is not one of the above types, don't care + default: + continue; + } + + // If we have no sig yet, take the first we can get + if (mostRecentSig == null) { + mostRecentSig = sig; + continue; + } + + // If the new sig is less recent, skip it + if (mostRecentSig.getCreationTime().after(sig.getCreationTime())) { + continue; + } + + // Otherwise, note it down as the new "most recent" one + mostRecentSig = sig; + } + + // Initialize to 0 as cached but empty value, if there is no sig (can't happen + // for canonicalized keyring), or there is no KEY_FLAGS packet in the sig + mCacheUsage = 0; + if (mostRecentSig != null) { + // If a mostRecentSig has been found, (cache and) return its flags + PGPSignatureSubpacketVector hashed = mostRecentSig.getHashedSubPackets(); if (hashed != null && hashed.getSubpacket(SignatureSubpacketTags.KEY_FLAGS) != null) { - // init if at least one key flag subpacket has been found - if (mCacheUsage == null) { - mCacheUsage = 0; - } - mCacheUsage |= hashed.getKeyFlags(); + mCacheUsage = hashed.getKeyFlags(); } } + } return mCacheUsage; } + + // this method relies on UNSAFE assumptions about the keyring, and should ONLY be used for + // TEST CASES!! + Date getUnsafeExpiryTimeForTesting () { + long valid = mPublicKey.getValidSeconds(); + + if (valid > Integer.MAX_VALUE) { + Log.e(Constants.TAG, "error, expiry time too large"); + return null; + } + if (valid == 0) { + // no expiry + return null; + } + Date creationDate = getCreationTime(); + Calendar calendar = GregorianCalendar.getInstance(); + calendar.setTime(creationDate); + calendar.add(Calendar.SECOND, (int) valid); + + return calendar.getTime(); + } } 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 93afb987a..c6fad1a73 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.SignatureSubpacket; import org.spongycastle.bcpg.SignatureSubpacketTags; import org.spongycastle.bcpg.sig.Exportable; +import org.spongycastle.bcpg.sig.NotationData; import org.spongycastle.bcpg.sig.Revocable; import org.spongycastle.bcpg.sig.RevocationReason; import org.spongycastle.openpgp.PGPException; @@ -28,15 +29,16 @@ import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; -import java.security.SignatureException; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; /** OpenKeychain wrapper around PGPSignature objects. * @@ -76,8 +78,12 @@ public class WrappedSignature { return mSig.getCreationTime(); } + public long getKeyExpirySeconds() { + return mSig.getHashedSubPackets().getKeyExpirationTime(); + } + public ArrayList<WrappedSignature> getEmbeddedSignatures() { - ArrayList<WrappedSignature> sigs = new ArrayList<WrappedSignature>(); + ArrayList<WrappedSignature> sigs = new ArrayList<>(); if (!mSig.hasSubpackets()) { return sigs; } @@ -197,12 +203,23 @@ public class WrappedSignature { } } + boolean verifySignature(PGPPublicKey key, PGPUserAttributeSubpacketVector attribute) throws PgpGeneralException { + try { + return mSig.verifyCertification(attribute, key); + } catch (PGPException e) { + throw new PgpGeneralException("Error!", e); + } + } + public boolean verifySignature(UncachedPublicKey key, byte[] rawUserId) throws PgpGeneralException { return verifySignature(key.getPublicKey(), rawUserId); } public boolean verifySignature(CanonicalizedPublicKey key, String uid) throws PgpGeneralException { return verifySignature(key.getPublicKey(), uid); } + public boolean verifySignature(UncachedPublicKey key, WrappedUserAttribute attribute) throws PgpGeneralException { + return verifySignature(key.getPublicKey(), attribute.getVector()); + } public static WrappedSignature fromBytes(byte[] data) { PGPObjectFactory factory = new PGPObjectFactory(data); @@ -239,4 +256,20 @@ public class WrappedSignature { SignatureSubpacket p = mSig.getHashedSubPackets().getSubpacket(SignatureSubpacketTags.EXPORTABLE); return ! ((Exportable) p).isExportable(); } + + public HashMap<String,String> getNotation() { + HashMap<String,String> result = new HashMap<>(); + + // If there is any notation data + if (mSig.getHashedSubPackets() != null + && mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.NOTATION_DATA)) { + // Iterate over notation data + for (NotationData data : mSig.getHashedSubPackets().getNotationDataOccurrences()) { + result.put(data.getNotationName(), data.getNotationValue()); + } + } + + return result; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java new file mode 100644 index 000000000..8e23d36d9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.Packet; +import org.spongycastle.bcpg.UserAttributePacket; +import org.spongycastle.bcpg.UserAttributeSubpacket; +import org.spongycastle.bcpg.UserAttributeSubpacketInputStream; +import org.spongycastle.bcpg.UserAttributeSubpacketTags; +import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.ArrayList; + +public class WrappedUserAttribute implements Serializable { + + public static final int UAT_NONE = 0; + public static final int UAT_IMAGE = UserAttributeSubpacketTags.IMAGE_ATTRIBUTE; + + private PGPUserAttributeSubpacketVector mVector; + + WrappedUserAttribute(PGPUserAttributeSubpacketVector vector) { + mVector = vector; + } + + PGPUserAttributeSubpacketVector getVector() { + return mVector; + } + + public int getType() { + UserAttributeSubpacket[] subpackets = mVector.toSubpacketArray(); + if (subpackets.length > 0) { + return subpackets[0].getType(); + } + return 0; + } + + public static WrappedUserAttribute fromSubpacket (int type, byte[] data) { + UserAttributeSubpacket subpacket = new UserAttributeSubpacket(type, data); + PGPUserAttributeSubpacketVector vector = new PGPUserAttributeSubpacketVector( + new UserAttributeSubpacket[] { subpacket }); + + return new WrappedUserAttribute(vector); + + } + + public byte[] getEncoded () throws IOException { + UserAttributeSubpacket[] subpackets = mVector.toSubpacketArray(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (UserAttributeSubpacket subpacket : subpackets) { + subpacket.encode(out); + } + return out.toByteArray(); + } + + public static WrappedUserAttribute fromData (byte[] data) throws IOException { + UserAttributeSubpacketInputStream in = + new UserAttributeSubpacketInputStream(new ByteArrayInputStream(data)); + ArrayList<UserAttributeSubpacket> list = new ArrayList<UserAttributeSubpacket>(); + while (in.available() > 0) { + list.add(in.readPacket()); + } + UserAttributeSubpacket[] result = new UserAttributeSubpacket[list.size()]; + list.toArray(result); + return new WrappedUserAttribute( + new PGPUserAttributeSubpacketVector(result)); + } + + /** Writes this object to an ObjectOutputStream. */ + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BCPGOutputStream bcpg = new BCPGOutputStream(baos); + bcpg.writePacket(new UserAttributePacket(mVector.toSubpacketArray())); + out.writeObject(baos.toByteArray()); + + } + + private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + + byte[] data = (byte[]) in.readObject(); + BCPGInputStream bcpg = new BCPGInputStream(new ByteArrayInputStream(data)); + Packet p = bcpg.readPacket(); + if ( ! UserAttributePacket.class.isInstance(p)) { + throw new IOException("Could not decode UserAttributePacket!"); + } + mVector = new PGPUserAttributeSubpacketVector(((UserAttributePacket) p).getSubpackets()); + + } + + private void readObjectNoData() throws ObjectStreamException { + } + + @Override + public boolean equals(Object o) { + if (!WrappedUserAttribute.class.isInstance(o)) { + return false; + } + return mVector.equals(((WrappedUserAttribute) o).mVector); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 6127002bb..5856589c4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -51,9 +51,11 @@ public class KeychainContract { String EXPIRY = "expiry"; } - interface UserIdsColumns { + interface UserPacketsColumns { String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID + String TYPE = "type"; // not a database id String USER_ID = "user_id"; // not a database id + String ATTRIBUTE_DATA = "attribute_data"; // not a database id String RANK = "rank"; // ONLY used for sorting! no key, no nothing! String IS_PRIMARY = "is_primary"; String IS_REVOKED = "is_revoked"; @@ -83,6 +85,11 @@ public class KeychainContract { String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name } + interface ApiAppsAllowedKeysColumns { + String KEY_ID = "key_id"; // not a database id + String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name + } + public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".provider"; private static final Uri BASE_CONTENT_URI_INTERNAL = Uri @@ -104,8 +111,9 @@ public class KeychainContract { public static final String BASE_API_APPS = "api_apps"; public static final String PATH_ACCOUNTS = "accounts"; + public static final String PATH_ALLOWED_KEYS = "allowed_keys"; - public static class KeyRings implements BaseColumns, KeysColumns, UserIdsColumns { + public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns { public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID; public static final String IS_REVOKED = KeysColumns.IS_REVOKED; public static final String VERIFIED = CertsColumns.VERIFIED; @@ -225,7 +233,7 @@ public class KeychainContract { } - public static class UserIds implements UserIdsColumns, BaseColumns { + public static class UserPackets implements UserPacketsColumns, BaseColumns { public static final String VERIFIED = "verified"; public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); @@ -303,10 +311,27 @@ public class KeychainContract { } } + public static class ApiAllowedKeys implements ApiAppsAllowedKeysColumns, BaseColumns { + public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() + .appendPath(BASE_API_APPS).build(); + + /** + * Use if multiple items get returned + */ + public static final String CONTENT_TYPE + = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps.allowed_keys"; + + public static Uri buildBaseUri(String packageName) { + return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ALLOWED_KEYS) + .build(); + } + } + public static class Certs implements CertsColumns, BaseColumns { - public static final String USER_ID = UserIdsColumns.USER_ID; + public static final String USER_ID = UserPacketsColumns.USER_ID; public static final String SIGNER_UID = "signer_user_id"; + public static final int UNVERIFIED = 0; public static final int VERIFIED_SECRET = 1; public static final int VERIFIED_SELF = 2; 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 84a50dc65..b88cd6006 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -29,11 +29,12 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns; import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity; import org.sufficientlysecure.keychain.util.Log; @@ -52,7 +53,7 @@ import java.io.IOException; */ public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 6; + private static final int DATABASE_VERSION = 8; static Boolean apgHack = false; private Context mContext; @@ -60,10 +61,11 @@ public class KeychainDatabase extends SQLiteOpenHelper { String KEY_RINGS_PUBLIC = "keyrings_public"; String KEY_RINGS_SECRET = "keyrings_secret"; String KEYS = "keys"; - String USER_IDS = "user_ids"; + String USER_PACKETS = "user_ids"; String CERTS = "certs"; String API_APPS = "api_apps"; String API_ACCOUNTS = "api_accounts"; + String API_ALLOWED_KEYS = "api_allowed_keys"; } private static final String CREATE_KEYRINGS_PUBLIC = @@ -106,18 +108,20 @@ public class KeychainDatabase extends SQLiteOpenHelper { + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" + ")"; - private static final String CREATE_USER_IDS = - "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS + "(" - + UserIdsColumns.MASTER_KEY_ID + " INTEGER, " - + UserIdsColumns.USER_ID + " TEXT, " + private static final String CREATE_USER_PACKETS = + "CREATE TABLE IF NOT EXISTS " + Tables.USER_PACKETS + "(" + + UserPacketsColumns.MASTER_KEY_ID + " INTEGER, " + + UserPacketsColumns.TYPE + " INT, " + + UserPacketsColumns.USER_ID + " TEXT, " + + UserPacketsColumns.ATTRIBUTE_DATA + " BLOB, " - + UserIdsColumns.IS_PRIMARY + " INTEGER, " - + UserIdsColumns.IS_REVOKED + " INTEGER, " - + UserIdsColumns.RANK+ " INTEGER, " + + UserPacketsColumns.IS_PRIMARY + " INTEGER, " + + UserPacketsColumns.IS_REVOKED + " INTEGER, " + + UserPacketsColumns.RANK+ " INTEGER, " - + "PRIMARY KEY(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.USER_ID + "), " - + "UNIQUE (" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + "), " - + "FOREIGN KEY(" + UserIdsColumns.MASTER_KEY_ID + ") REFERENCES " + + "PRIMARY KEY(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.USER_ID + "), " + + "UNIQUE (" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + "), " + + "FOREIGN KEY(" + UserPacketsColumns.MASTER_KEY_ID + ") REFERENCES " + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" + ")"; @@ -138,7 +142,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ") REFERENCES " + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE," + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ") REFERENCES " - + Tables.USER_IDS + "(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + ") ON DELETE CASCADE" + + Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE" + ")"; private static final String CREATE_API_APPS = @@ -164,6 +168,18 @@ public class KeychainDatabase extends SQLiteOpenHelper { + Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + ")"; + private static final String CREATE_API_APPS_ALLOWED_KEYS = + "CREATE TABLE IF NOT EXISTS " + Tables.API_ALLOWED_KEYS + " (" + + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + ApiAppsAllowedKeysColumns.KEY_ID + " INTEGER, " + + ApiAppsAllowedKeysColumns.PACKAGE_NAME + " TEXT NOT NULL, " + + + "UNIQUE(" + ApiAppsAllowedKeysColumns.KEY_ID + ", " + + ApiAppsAllowedKeysColumns.PACKAGE_NAME + "), " + + "FOREIGN KEY(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") REFERENCES " + + Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + + ")"; + KeychainDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); mContext = context; @@ -189,10 +205,11 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL(CREATE_KEYRINGS_PUBLIC); db.execSQL(CREATE_KEYRINGS_SECRET); db.execSQL(CREATE_KEYS); - db.execSQL(CREATE_USER_IDS); + db.execSQL(CREATE_USER_PACKETS); db.execSQL(CREATE_CERTS); db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_API_APPS_ACCOUNTS); + db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); } @Override @@ -238,6 +255,18 @@ public class KeychainDatabase extends SQLiteOpenHelper { case 5: // do consolidate for 3.0 beta3 // fall through + case 6: + db.execSQL("ALTER TABLE user_ids ADD COLUMN type INTEGER"); + db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB"); + case 7: + // consolidate + case 8: + // new table for allowed key ids in API + try { + db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); + } catch (Exception e) { + // never mind, the column probably already existed + } } // always do consolidate after upgrade diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index d40287690..2601c1f69 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -32,12 +32,14 @@ import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.util.Log; @@ -62,9 +64,10 @@ public class KeychainProvider extends ContentProvider { private static final int KEY_RING_CERTS_SPECIFIC = 206; private static final int API_APPS = 301; - private static final int API_APPS_BY_PACKAGE_NAME = 303; - private static final int API_ACCOUNTS = 304; - private static final int API_ACCOUNTS_BY_ACCOUNT_NAME = 306; + private static final int API_APPS_BY_PACKAGE_NAME = 302; + private static final int API_ACCOUNTS = 303; + private static final int API_ACCOUNTS_BY_ACCOUNT_NAME = 304; + private static final int API_ALLOWED_KEYS = 305; private static final int KEY_RINGS_FIND_BY_EMAIL = 400; private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; @@ -161,6 +164,8 @@ public class KeychainProvider extends ContentProvider { * * api_apps/_/accounts * api_apps/_/accounts/_ (account name) + * + * api_apps/_/allowed_keys * </pre> */ matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS); @@ -171,6 +176,9 @@ public class KeychainProvider extends ContentProvider { matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/" + KeychainContract.PATH_ACCOUNTS + "/*", API_ACCOUNTS_BY_ACCOUNT_NAME); + matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/" + + KeychainContract.PATH_ALLOWED_KEYS, API_ALLOWED_KEYS); + return matcher; } @@ -205,7 +213,7 @@ public class KeychainProvider extends ContentProvider { return Keys.CONTENT_TYPE; case KEY_RING_USER_IDS: - return UserIds.CONTENT_TYPE; + return UserPackets.CONTENT_TYPE; case KEY_RING_SECRET: return KeyRings.CONTENT_ITEM_TYPE; @@ -222,6 +230,9 @@ public class KeychainProvider extends ContentProvider { case API_ACCOUNTS_BY_ACCOUNT_NAME: return ApiAccounts.CONTENT_ITEM_TYPE; + case API_ALLOWED_KEYS: + return ApiAllowedKeys.CONTENT_TYPE; + default: throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -247,7 +258,7 @@ public class KeychainProvider extends ContentProvider { case KEY_RINGS_UNIFIED: case KEY_RINGS_FIND_BY_EMAIL: case KEY_RINGS_FIND_BY_SUBKEY: { - HashMap<String, String> projectionMap = new HashMap<String, String>(); + HashMap<String, String> projectionMap = new HashMap<>(); projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id"); projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); projectionMap.put(KeyRings.KEY_ID, Tables.KEYS + "." + Keys.KEY_ID); @@ -262,7 +273,7 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(KeyRings.EXPIRY, Tables.KEYS + "." + Keys.EXPIRY); projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM); projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT); - projectionMap.put(KeyRings.USER_ID, UserIds.USER_ID); + projectionMap.put(KeyRings.USER_ID, UserPackets.USER_ID); projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED); projectionMap.put(KeyRings.PUBKEY_DATA, Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.KEY_RING_DATA @@ -296,11 +307,12 @@ public class KeychainProvider extends ContentProvider { qb.setTables( Tables.KEYS - + " INNER JOIN " + Tables.USER_IDS + " ON (" + + " INNER JOIN " + Tables.USER_PACKETS + " ON (" + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = " - + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID - + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = 0" + + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + // we KNOW that the rank zero user packet is a user id! + + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = 0" + ") LEFT JOIN " + Tables.CERTS + " ON (" + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = " @@ -376,7 +388,7 @@ public class KeychainProvider extends ContentProvider { String subkey = Long.valueOf(uri.getLastPathSegment()).toString(); qb.appendWhere(" AND EXISTS (" + " SELECT 1 FROM " + Tables.KEYS + " AS tmp" - + " WHERE tmp." + UserIds.MASTER_KEY_ID + + " WHERE tmp." + UserPackets.MASTER_KEY_ID + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " AND tmp." + Keys.KEY_ID + " = " + subkey + "" + ")"); @@ -398,15 +410,15 @@ public class KeychainProvider extends ContentProvider { if (i != 0) { emailWhere += " OR "; } - emailWhere += "tmp." + UserIds.USER_ID + " LIKE "; + emailWhere += "tmp." + UserPackets.USER_ID + " LIKE "; // match '*<email>', so it has to be at the *end* of the user id emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">"); gotCondition = true; } if(gotCondition) { qb.appendWhere(" AND EXISTS (" - + " SELECT 1 FROM " + Tables.USER_IDS + " AS tmp" - + " WHERE tmp." + UserIds.MASTER_KEY_ID + + " SELECT 1 FROM " + Tables.USER_PACKETS + " AS tmp" + + " WHERE tmp." + UserPackets.MASTER_KEY_ID + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " AND (" + emailWhere + ")" + ")"); @@ -420,7 +432,7 @@ public class KeychainProvider extends ContentProvider { } if (TextUtils.isEmpty(sortOrder)) { - sortOrder = Tables.USER_IDS + "." + UserIds.USER_ID + " ASC"; + sortOrder = Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC"; } // uri to watch is all /key_rings/ @@ -430,7 +442,7 @@ public class KeychainProvider extends ContentProvider { } case KEY_RING_KEYS: { - HashMap<String, String> projectionMap = new HashMap<String, String>(); + HashMap<String, String> projectionMap = new HashMap<>(); projectionMap.put(Keys._ID, Tables.KEYS + ".oid AS _id"); projectionMap.put(Keys.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); projectionMap.put(Keys.RANK, Tables.KEYS + "." + Keys.RANK); @@ -458,37 +470,45 @@ public class KeychainProvider extends ContentProvider { case KEY_RINGS_USER_IDS: case KEY_RING_USER_IDS: { - HashMap<String, String> projectionMap = new HashMap<String, String>(); - projectionMap.put(UserIds._ID, Tables.USER_IDS + ".oid AS _id"); - projectionMap.put(UserIds.MASTER_KEY_ID, Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID); - projectionMap.put(UserIds.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); - projectionMap.put(UserIds.RANK, Tables.USER_IDS + "." + UserIds.RANK); - projectionMap.put(UserIds.IS_PRIMARY, Tables.USER_IDS + "." + UserIds.IS_PRIMARY); - projectionMap.put(UserIds.IS_REVOKED, Tables.USER_IDS + "." + UserIds.IS_REVOKED); + HashMap<String, String> projectionMap = new HashMap<>(); + projectionMap.put(UserPackets._ID, Tables.USER_PACKETS + ".oid AS _id"); + projectionMap.put(UserPackets.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID); + projectionMap.put(UserPackets.TYPE, Tables.USER_PACKETS + "." + UserPackets.TYPE); + projectionMap.put(UserPackets.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID); + projectionMap.put(UserPackets.ATTRIBUTE_DATA, Tables.USER_PACKETS + "." + UserPackets.ATTRIBUTE_DATA); + projectionMap.put(UserPackets.RANK, Tables.USER_PACKETS + "." + UserPackets.RANK); + projectionMap.put(UserPackets.IS_PRIMARY, Tables.USER_PACKETS + "." + UserPackets.IS_PRIMARY); + projectionMap.put(UserPackets.IS_REVOKED, Tables.USER_PACKETS + "." + UserPackets.IS_REVOKED); // we take the minimum (>0) here, where "1" is "verified by known secret key" - projectionMap.put(UserIds.VERIFIED, "MIN(" + Certs.VERIFIED + ") AS " + UserIds.VERIFIED); + projectionMap.put(UserPackets.VERIFIED, "MIN(" + Certs.VERIFIED + ") AS " + UserPackets.VERIFIED); qb.setProjectionMap(projectionMap); - qb.setTables(Tables.USER_IDS + qb.setTables(Tables.USER_PACKETS + " LEFT JOIN " + Tables.CERTS + " ON (" - + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = " + + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = " + Tables.CERTS + "." + Certs.MASTER_KEY_ID - + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = " + + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = " + Tables.CERTS + "." + Certs.RANK + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0" + ")"); - groupBy = Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID - + ", " + Tables.USER_IDS + "." + UserIds.RANK; + groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + + ", " + Tables.USER_PACKETS + "." + UserPackets.RANK; + + // for now, we only respect user ids here, so TYPE must be NULL + // TODO expand with KEY_RING_USER_PACKETS query type which lifts this restriction + qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"); // If we are searching for a particular keyring's ids, add where if (match == KEY_RING_USER_IDS) { - qb.appendWhere(Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = "); + // TODO remove with the thing above + qb.appendWhere(" AND "); + qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); } if (TextUtils.isEmpty(sortOrder)) { - sortOrder = Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " ASC" - + "," + Tables.USER_IDS + "." + UserIds.RANK + " ASC"; + sortOrder = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC" + + "," + Tables.USER_PACKETS + "." + UserPackets.RANK + " ASC"; } break; @@ -497,7 +517,7 @@ public class KeychainProvider extends ContentProvider { case KEY_RINGS_PUBLIC: case KEY_RING_PUBLIC: { - HashMap<String, String> projectionMap = new HashMap<String, String>(); + HashMap<String, String> projectionMap = new HashMap<>(); projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_PUBLIC + ".oid AS _id"); projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID); projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA); @@ -515,7 +535,7 @@ public class KeychainProvider extends ContentProvider { case KEY_RINGS_SECRET: case KEY_RING_SECRET: { - HashMap<String, String> projectionMap = new HashMap<String, String>(); + HashMap<String, String> projectionMap = new HashMap<>(); projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_SECRET + ".oid AS _id"); projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID); projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA); @@ -533,7 +553,7 @@ public class KeychainProvider extends ContentProvider { case KEY_RING_CERTS: case KEY_RING_CERTS_SPECIFIC: { - HashMap<String, String> projectionMap = new HashMap<String, String>(); + HashMap<String, String> projectionMap = new HashMap<>(); projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID); projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID); projectionMap.put(Certs.RANK, Tables.CERTS + "." + Certs.RANK); @@ -542,20 +562,24 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION); projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER); projectionMap.put(Certs.DATA, Tables.CERTS + "." + Certs.DATA); - projectionMap.put(Certs.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); - projectionMap.put(Certs.SIGNER_UID, "signer." + UserIds.USER_ID + " AS " + Certs.SIGNER_UID); + projectionMap.put(Certs.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID); + projectionMap.put(Certs.SIGNER_UID, "signer." + UserPackets.USER_ID + " AS " + Certs.SIGNER_UID); qb.setProjectionMap(projectionMap); qb.setTables(Tables.CERTS - + " JOIN " + Tables.USER_IDS + " ON (" + + " JOIN " + Tables.USER_PACKETS + " ON (" + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " - + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AND " + Tables.CERTS + "." + Certs.RANK + " = " - + Tables.USER_IDS + "." + UserIds.RANK - + ") LEFT JOIN " + Tables.USER_IDS + " AS signer ON (" + + Tables.USER_PACKETS + "." + UserPackets.RANK + // for now, we only return user ids here, so TYPE must be NULL + // TODO at some point, we should lift this restriction + + " AND " + + Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL" + + ") LEFT JOIN " + Tables.USER_PACKETS + " AS signer ON (" + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = " - + "signer." + UserIds.MASTER_KEY_ID + + "signer." + UserPackets.MASTER_KEY_ID + " AND " + "signer." + Keys.RANK + " = 0" + ")"); @@ -600,6 +624,12 @@ public class KeychainProvider extends ContentProvider { qb.appendWhereEscapeString(uri.getLastPathSegment()); break; + case API_ALLOWED_KEYS: + qb.setTables(Tables.API_ALLOWED_KEYS); + qb.appendWhere(Tables.API_ALLOWED_KEYS + "." + ApiAccounts.PACKAGE_NAME + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); + + break; default: throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); @@ -662,8 +692,18 @@ public class KeychainProvider extends ContentProvider { break; case KEY_RING_USER_IDS: - db.insertOrThrow(Tables.USER_IDS, null, values); - keyId = values.getAsLong(UserIds.MASTER_KEY_ID); + // iff TYPE is null, user_id MUST be null as well + if ( ! (values.get(UserPacketsColumns.TYPE) == null + ? (values.get(UserPacketsColumns.USER_ID) != null && values.get(UserPacketsColumns.ATTRIBUTE_DATA) == null) + : (values.get(UserPacketsColumns.ATTRIBUTE_DATA) != null && values.get(UserPacketsColumns.USER_ID) == null) + )) { + throw new AssertionError("Incorrect type for user packet! This is a bug!"); + } + if (((Number)values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) { + throw new AssertionError("Rank 0 user packet must be a user id!"); + } + db.insertOrThrow(Tables.USER_PACKETS, null, values); + keyId = values.getAsLong(UserPackets.MASTER_KEY_ID); break; case KEY_RING_CERTS: @@ -677,7 +717,7 @@ public class KeychainProvider extends ContentProvider { db.insertOrThrow(Tables.API_APPS, null, values); break; - case API_ACCOUNTS: + case API_ACCOUNTS: { // set foreign key automatically based on given uri // e.g., api_apps/com.example.app/accounts/ String packageName = uri.getPathSegments().get(1); @@ -685,12 +725,21 @@ public class KeychainProvider extends ContentProvider { db.insertOrThrow(Tables.API_ACCOUNTS, null, values); break; + } + case API_ALLOWED_KEYS: { + // set foreign key automatically based on given uri + // e.g., api_apps/com.example.app/allowed_keys/ + String packageName = uri.getPathSegments().get(1); + values.put(ApiAllowedKeys.PACKAGE_NAME, packageName); + db.insertOrThrow(Tables.API_ALLOWED_KEYS, null, values); + break; + } default: throw new UnsupportedOperationException("Unknown uri: " + uri); } - if(keyId != null) { + if (keyId != null) { uri = KeyRings.buildGenericKeyRingUri(keyId); rowUri = uri; } @@ -753,6 +802,10 @@ public class KeychainProvider extends ContentProvider { count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, additionalSelection), selectionArgs); break; + case API_ALLOWED_KEYS: + count = db.delete(Tables.API_ALLOWED_KEYS, buildDefaultApiAllowedKeysSelection(uri, additionalSelection), + selectionArgs); + break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -845,4 +898,15 @@ public class KeychainProvider extends ContentProvider { + andSelection; } + private String buildDefaultApiAllowedKeysSelection(Uri uri, String selection) { + String packageName = DatabaseUtils.sqlEscapeString(uri.getPathSegments().get(1)); + + String andSelection = ""; + if (!TextUtils.isEmpty(selection)) { + andSelection = " AND (" + selection + ")"; + } + + return ApiAllowedKeys.PACKAGE_NAME + "=" + packageName + andSelection; + } + } 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 05dc99c5e..d947ae053 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -30,11 +30,13 @@ import android.support.v4.util.LongSparseArray; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.ImportExportOperation; +import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; @@ -42,27 +44,27 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.operations.ImportExportOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.WrappedSignature; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AppSettings; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; -import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; -import org.sufficientlysecure.keychain.util.ParcelableFileCache; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableFileCache; +import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ProgressFixedScaler; import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.Utf8Util; @@ -169,7 +171,7 @@ public class ProviderHelper { Cursor cursor = mContentResolver.query(uri, proj, selection, null, null); try { - HashMap<String, Object> result = new HashMap<String, Object>(proj.length); + HashMap<String, Object> result = new HashMap<>(proj.length); if (cursor != null && cursor.moveToFirst()) { int pos = 0; for (String p : proj) { @@ -192,6 +194,9 @@ public class ProviderHelper { } pos += 1; } + } else { + // If no data was found, throw an appropriate exception + throw new NotFoundException(); } return result; @@ -217,7 +222,7 @@ public class ProviderHelper { }, KeyRings.HAS_ANY_SECRET + " = 1", null, null); try { - LongSparseArray<CanonicalizedPublicKey> result = new LongSparseArray<CanonicalizedPublicKey>(); + LongSparseArray<CanonicalizedPublicKey> result = new LongSparseArray<>(); if (cursor != null && cursor.moveToFirst()) do { long masterKeyId = cursor.getLong(0); @@ -346,7 +351,7 @@ public class ProviderHelper { mIndent += 1; // save all keys and userIds included in keyRing object in database - operations = new ArrayList<ContentProviderOperation>(); + operations = new ArrayList<>(); log(LogType.MSG_IP_INSERT_KEYRING); { // insert keyring @@ -436,18 +441,18 @@ 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. + List<UserPacketItem> uids = new ArrayList<>(); + if (trustedKeys.size() == 0) { log(LogType.MSG_IP_UID_CLASSIFYING_ZERO); } else { log(LogType.MSG_IP_UID_CLASSIFYING, trustedKeys.size()); } mIndent += 1; - List<UserIdItem> uids = new ArrayList<UserIdItem>(); - for (byte[] rawUserId : new IterableIterator<byte[]>( - masterKey.getUnorderedRawUserIds().iterator())) { + for (byte[] rawUserId : masterKey.getUnorderedRawUserIds()) { String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); - UserIdItem item = new UserIdItem(); + UserPacketItem item = new UserPacketItem(); uids.add(item); item.userId = userId; @@ -456,7 +461,7 @@ public class ProviderHelper { log(LogType.MSG_IP_UID_PROCESSING, userId); mIndent += 1; // look through signatures for this specific key - for (WrappedSignature cert : new IterableIterator<WrappedSignature>( + for (WrappedSignature cert : new IterableIterator<>( masterKey.getSignaturesForRawId(rawUserId))) { long certId = cert.getKeyId(); // self signature @@ -468,7 +473,7 @@ public class ProviderHelper { item.selfCert = cert; item.isPrimary = cert.isPrimaryUserId(); } else { - item.isRevoked = true; + item.selfRevocation = cert; log(LogType.MSG_IP_UID_REVOKED); } continue; @@ -530,6 +535,106 @@ public class ProviderHelper { } mIndent -= 1; + ArrayList<WrappedUserAttribute> userAttributes = masterKey.getUnorderedUserAttributes(); + // Don't spam the log if there aren't even any attributes + if ( ! userAttributes.isEmpty()) { + log(LogType.MSG_IP_UAT_CLASSIFYING); + } + + mIndent += 1; + for (WrappedUserAttribute userAttribute : userAttributes) { + + UserPacketItem item = new UserPacketItem(); + uids.add(item); + item.type = userAttribute.getType(); + item.attributeData = userAttribute.getEncoded(); + + int unknownCerts = 0; + + switch (item.type) { + case WrappedUserAttribute.UAT_IMAGE: + log(LogType.MSG_IP_UAT_PROCESSING_IMAGE); + break; + default: + log(LogType.MSG_IP_UAT_PROCESSING_UNKNOWN); + break; + } + mIndent += 1; + // look through signatures for this specific key + for (WrappedSignature cert : new IterableIterator<>( + masterKey.getSignaturesForUserAttribute(userAttribute))) { + long certId = cert.getKeyId(); + // self signature + if (certId == masterKeyId) { + + // NOTE self-certificates are already verified during canonicalization, + // AND we know there is at most one cert plus at most one revocation + // AND the revocation only exists if there is no newer certification + if (!cert.isRevocation()) { + item.selfCert = cert; + } else { + item.selfRevocation = cert; + log(LogType.MSG_IP_UAT_REVOKED); + } + continue; + + } + + // do we have a trusted key for this? + if (trustedKeys.indexOfKey(certId) < 0) { + unknownCerts += 1; + continue; + } + + // verify signatures from known private keys + CanonicalizedPublicKey trustedKey = trustedKeys.get(certId); + + try { + cert.init(trustedKey); + // if it doesn't certify, leave a note and skip + if ( ! cert.verifySignature(masterKey, userAttribute)) { + log(LogType.MSG_IP_UAT_CERT_BAD); + continue; + } + + log(cert.isRevocation() + ? LogType.MSG_IP_UAT_CERT_GOOD_REVOKE + : LogType.MSG_IP_UAT_CERT_GOOD, + KeyFormattingUtils.convertKeyIdToHexShort(trustedKey.getKeyId()) + ); + + // check if there is a previous certificate + WrappedSignature prev = item.trustedCerts.get(cert.getKeyId()); + if (prev != null) { + // if it's newer, skip this one + if (prev.getCreationTime().after(cert.getCreationTime())) { + log(LogType.MSG_IP_UAT_CERT_OLD); + continue; + } + // if the previous one was a non-revokable certification, no need to look further + if (!prev.isRevocation() && !prev.isRevokable()) { + log(LogType.MSG_IP_UAT_CERT_NONREVOKE); + continue; + } + log(LogType.MSG_IP_UAT_CERT_NEW); + } + item.trustedCerts.put(cert.getKeyId(), cert); + + } catch (PgpGeneralException e) { + log(LogType.MSG_IP_UAT_CERT_ERROR, + KeyFormattingUtils.convertKeyIdToHex(cert.getKeyId())); + } + + } + + if (unknownCerts > 0) { + log(LogType.MSG_IP_UAT_CERTS_UNKNOWN, unknownCerts); + } + mIndent -= 1; + + } + mIndent -= 1; + progress.setProgress(LogType.MSG_IP_UID_REORDER.getMsgId(), 65, 100); log(LogType.MSG_IP_UID_REORDER); // primary before regular before revoked (see UserIdItem.compareTo) @@ -537,17 +642,23 @@ public class ProviderHelper { Collections.sort(uids); // iterate and put into db for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) { - UserIdItem item = uids.get(userIdRank); + UserPacketItem item = uids.get(userIdRank); operations.add(buildUserIdOperations(masterKeyId, item, userIdRank)); - if (item.selfCert != null) { - operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert, - selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF)); + + if (item.selfCert == null) { + throw new AssertionError("User ids MUST be self-certified at this point!!"); } - // don't bother with trusted certs if the uid is revoked, anyways - if (item.isRevoked) { + + if (item.selfRevocation != null) { + operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfRevocation, + Certs.VERIFIED_SELF)); + // don't bother with trusted certs if the uid is revoked, anyways continue; } + operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert, + selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF)); + // iterate over signatures for (int i = 0; i < item.trustedCerts.size() ; i++) { WrappedSignature sig = item.trustedCerts.valueAt(i); @@ -601,23 +712,32 @@ public class ProviderHelper { } - private static class UserIdItem implements Comparable<UserIdItem> { + private static class UserPacketItem implements Comparable<UserPacketItem> { + Integer type; String userId; + byte[] attributeData; boolean isPrimary = false; - boolean isRevoked = false; WrappedSignature selfCert; - LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<WrappedSignature>(); + WrappedSignature selfRevocation; + LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<>(); @Override - public int compareTo(UserIdItem o) { + public int compareTo(UserPacketItem o) { + // revoked keys always come last! + //noinspection DoubleNegation + if ( (selfRevocation != null) != (o.selfRevocation != null)) { + return selfRevocation != null ? 1 : -1; + } + // if one is a user id, but the other isn't, the user id always comes first. + // we compare for null values here, so != is the correct operator! + // noinspection NumberEquality + if (type != o.type) { + return type == null ? -1 : 1; + } // if one key is primary but the other isn't, the primary one always comes first if (isPrimary != o.isPrimary) { return isPrimary ? -1 : 1; } - // revoked keys always come last! - if (isRevoked != o.isRevoked) { - return isRevoked ? 1 : -1; - } return 0; } } @@ -682,6 +802,11 @@ public class ProviderHelper { KeyFormattingUtils.convertKeyIdToHex(id) ); break; + case PIN: + log(LogType.MSG_IS_SUBKEY_PIN, + KeyFormattingUtils.convertKeyIdToHex(id) + ); + break; case GNU_DUMMY: log(LogType.MSG_IS_SUBKEY_STRIPPED, KeyFormattingUtils.convertKeyIdToHex(id) @@ -936,6 +1061,11 @@ public class ProviderHelper { log.add(LogType.MSG_CON, indent); indent += 1; + if (mConsolidateCritical) { + log.add(LogType.MSG_CON_RECURSIVE, indent); + return new ConsolidateResult(ConsolidateResult.RESULT_OK, log); + } + progress.setProgress(R.string.progress_con_saving, 0, 100); // The consolidate operation can never be cancelled! @@ -958,7 +1088,7 @@ public class ProviderHelper { // No keys existing might be a legitimate option, we write an empty file in that case cursor.moveToFirst(); ParcelableFileCache<ParcelableKeyRing> cache = - new ParcelableFileCache<ParcelableKeyRing>(mContext, "consolidate_secret.pcl"); + new ParcelableFileCache<>(mContext, "consolidate_secret.pcl"); cache.writeCache(cursor.getCount(), new Iterator<ParcelableKeyRing>() { ParcelableKeyRing ring; @@ -1020,7 +1150,7 @@ public class ProviderHelper { // No keys existing might be a legitimate option, we write an empty file in that case cursor.moveToFirst(); ParcelableFileCache<ParcelableKeyRing> cache = - new ParcelableFileCache<ParcelableKeyRing>(mContext, "consolidate_public.pcl"); + new ParcelableFileCache<>(mContext, "consolidate_public.pcl"); cache.writeCache(cursor.getCount(), new Iterator<ParcelableKeyRing>() { ParcelableKeyRing ring; @@ -1102,13 +1232,11 @@ public class ProviderHelper { log.add(LogType.MSG_CON_DB_CLEAR, indent); mContentResolver.delete(KeyRings.buildUnifiedKeyRingsUri(), null, null); - ParcelableFileCache<ParcelableKeyRing> cacheSecret = - new ParcelableFileCache<ParcelableKeyRing>(mContext, "consolidate_secret.pcl"); - ParcelableFileCache<ParcelableKeyRing> cachePublic = - new ParcelableFileCache<ParcelableKeyRing>(mContext, "consolidate_public.pcl"); + ParcelableFileCache<ParcelableKeyRing> cacheSecret, cachePublic; // Set flag that we have a cached consolidation here try { + cacheSecret = new ParcelableFileCache<>(mContext, "consolidate_secret.pcl"); IteratorWithSize<ParcelableKeyRing> itSecrets = cacheSecret.readCache(false); int numSecrets = itSecrets.getSize(); @@ -1136,6 +1264,7 @@ public class ProviderHelper { try { + cachePublic = new ParcelableFileCache<>(mContext, "consolidate_public.pcl"); IteratorWithSize<ParcelableKeyRing> itPublics = cachePublic.readCache(); int numPublics = itPublics.getSize(); @@ -1191,7 +1320,6 @@ public class ProviderHelper { progress.setProgress(100, 100); log.add(LogType.MSG_CON_SUCCESS, indent); - indent -= 1; return new ConsolidateResult(ConsolidateResult.RESULT_OK, log); @@ -1225,15 +1353,17 @@ public class ProviderHelper { * Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing */ private ContentProviderOperation - buildUserIdOperations(long masterKeyId, UserIdItem item, int rank) { + buildUserIdOperations(long masterKeyId, UserPacketItem item, int rank) { ContentValues values = new ContentValues(); - values.put(UserIds.MASTER_KEY_ID, masterKeyId); - values.put(UserIds.USER_ID, item.userId); - values.put(UserIds.IS_PRIMARY, item.isPrimary); - values.put(UserIds.IS_REVOKED, item.isRevoked); - values.put(UserIds.RANK, rank); + values.put(UserPackets.MASTER_KEY_ID, masterKeyId); + values.put(UserPackets.TYPE, item.type); + values.put(UserPackets.USER_ID, item.userId); + values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData); + values.put(UserPackets.IS_PRIMARY, item.isPrimary); + values.put(UserPackets.IS_REVOKED, item.selfRevocation != null); + values.put(UserPackets.RANK, rank); - Uri uri = UserIds.buildUserIdsUri(masterKeyId); + Uri uri = UserPackets.buildUserIdsUri(masterKeyId); return ContentProviderOperation.newInsert(uri).withValues(values).build(); } @@ -1261,7 +1391,7 @@ public class ProviderHelper { public ArrayList<String> getRegisteredApiApps() { Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null); - ArrayList<String> packageNames = new ArrayList<String>(); + ArrayList<String> packageNames = new ArrayList<>(); try { if (cursor != null) { int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME); @@ -1366,7 +1496,7 @@ public class ProviderHelper { } public Set<Long> getAllKeyIdsForApp(Uri uri) { - Set<Long> keyIds = new HashSet<Long>(); + Set<Long> keyIds = new HashSet<>(); Cursor cursor = mContentResolver.query(uri, null, null, null, null); try { @@ -1385,8 +1515,41 @@ public class ProviderHelper { return keyIds; } + public Set<Long> getAllowedKeyIdsForApp(Uri uri) { + Set<Long> keyIds = new HashSet<>(); + + Cursor cursor = mContentResolver.query(uri, null, null, null, null); + try { + if (cursor != null) { + int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAllowedKeys.KEY_ID); + while (cursor.moveToNext()) { + keyIds.add(cursor.getLong(keyIdColumn)); + } + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + return keyIds; + } + + public void saveAllowedKeyIdsForApp(Uri uri, Set<Long> allowedKeyIds) + throws RemoteException, OperationApplicationException { + // wipe whole table of allowed keys for this account + mContentResolver.delete(uri, null, null); + + // re-insert allowed key ids + for (Long keyId : allowedKeyIds) { + ContentValues values = new ContentValues(); + values.put(ApiAllowedKeys.KEY_ID, keyId); + mContentResolver.insert(uri, values); + } + } + public Set<String> getAllFingerprints(Uri uri) { - Set<String> fingerprints = new HashSet<String>(); + Set<String> fingerprints = new HashSet<>(); String[] projection = new String[]{KeyRings.FINGERPRINT}; Cursor cursor = mContentResolver.query(uri, projection, null, null, null); try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index a5813567a..1a65b1bee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -26,35 +26,34 @@ import android.os.ParcelFileDescriptor; import android.text.TextUtils; import org.openintents.openpgp.IOpenPgpService; -import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpError; +import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.util.OpenPgpApi; import org.spongycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.ui.NfcActivity; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; +import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; +import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput; +import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; -import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.ui.ImportKeysActivity; +import org.sufficientlysecure.keychain.ui.NfcActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.ViewKeyActivity; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; @@ -85,9 +84,9 @@ public class OpenPgpService extends RemoteService { boolean missingUserIdsCheck = false; boolean duplicateUserIdsCheck = false; - ArrayList<Long> keyIds = new ArrayList<Long>(); - ArrayList<String> missingUserIds = new ArrayList<String>(); - ArrayList<String> duplicateUserIds = new ArrayList<String>(); + ArrayList<Long> keyIds = new ArrayList<>(); + ArrayList<String> missingUserIds = new ArrayList<>(); + ArrayList<String> duplicateUserIds = new ArrayList<>(); if (!noUserIdsCheck) { for (String email : encryptionUserIds) { // try to find the key for this specific email @@ -222,9 +221,12 @@ public class OpenPgpService extends RemoteService { } private Intent signImpl(Intent data, ParcelFileDescriptor input, - ParcelFileDescriptor output, AccountSettings accSettings) { + ParcelFileDescriptor output, AccountSettings accSettings, + boolean cleartextSign) { + InputStream is = null; + OutputStream os = null; try { - boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); + boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH); if (nfcSignedHash != null) { @@ -242,91 +244,55 @@ public class OpenPgpService extends RemoteService { } // Get Input- and OutputStream from ParcelFileDescriptor - InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); - OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output); - try { - long inputLength = is.available(); - InputData inputData = new InputData(is, inputLength); - - // Find the appropriate subkey to sign with - long sigSubKeyId; - try { - CachedPublicKeyRing signingRing = - new ProviderHelper(this).getCachedPublicKeyRing(accSettings.getKeyId()); - sigSubKeyId = signingRing.getSecretSignId(); - } catch (PgpKeyNotFoundException e) { - // secret key that is set for this account is deleted? - // show account config again! - return getCreateAccountIntent(data, getAccountName(data)); - } - - // get passphrase from cache, if key has "no" passphrase, this returns an empty String - String passphrase; - if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) { - passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); + is = new ParcelFileDescriptor.AutoCloseInputStream(input); + if (cleartextSign) { + // output stream only needed for cleartext signatures, + // detached signatures are returned as extra + os = new ParcelFileDescriptor.AutoCloseOutputStream(output); + } + long inputLength = is.available(); + InputData inputData = new InputData(is, inputLength); + + // sign-only + PgpSignEncryptInput pseInput = new PgpSignEncryptInput() + .setEnableAsciiArmorOutput(asciiArmor) + .setCleartextSignature(cleartextSign) + .setDetachedSignature(!cleartextSign) + .setVersionHeader(PgpHelper.getVersionForHeader(this)) + .setSignatureHashAlgorithm(accSettings.getHashAlgorithm()) + .setSignatureMasterKeyId(accSettings.getKeyId()) + .setNfcState(nfcSignedHash, nfcCreationDate); + + // execute PGP operation! + PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null); + PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputData, os); + + if (pgpResult.isPending()) { + if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) == + PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) { + return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded()); + } else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) == + PgpSignEncryptResult.RESULT_PENDING_NFC) { + // return PendingIntent to execute NFC activity + // pass through the signature creation timestamp to be used again on second execution + // of PgpSignEncrypt when we have the signed hash! + data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime()); + return getNfcSignIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo()); } else { - try { - passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), - accSettings.getKeyId(), sigSubKeyId); - } catch (PassphraseCacheService.KeyNotFoundException e) { - // should happen earlier, but return again here if it happens - return getCreateAccountIntent(data, getAccountName(data)); - } + throw new PgpGeneralException( + "Encountered unhandled type of pending action not supported by API!"); } - if (passphrase == null) { - // get PendingIntent for passphrase input, add it to given params and return to client - return getPassphraseIntent(data, sigSubKeyId); - } - - // sign-only - PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( - this, new ProviderHelper(getContext()), null, - inputData, os - ); - builder.setEnableAsciiArmorOutput(asciiArmor) - .setVersionHeader(PgpHelper.getVersionForHeader(this)) - .setSignatureHashAlgorithm(accSettings.getHashAlgorithm()) - .setSignatureMasterKeyId(accSettings.getKeyId()) - .setSignatureSubKeyId(sigSubKeyId) - .setSignaturePassphrase(passphrase) - .setNfcState(nfcSignedHash, nfcCreationDate); - - // TODO: currently always assume cleartext input, no sign-only of binary currently! - builder.setCleartextInput(true); - - // execute PGP operation! - SignEncryptResult pgpResult = builder.build().execute(); - - if (pgpResult.isPending()) { - if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_PASSPHRASE) == - SignEncryptResult.RESULT_PENDING_PASSPHRASE) { - return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded()); - } else if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_NFC) == - SignEncryptResult.RESULT_PENDING_NFC) { - // return PendingIntent to execute NFC activity - // pass through the signature creation timestamp to be used again on second execution - // of PgpSignEncrypt when we have the signed hash! - data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime()); - return getNfcSignIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo()); - } else { - throw new PgpGeneralException( - "Encountered unhandled type of pending action not supported by API!"); - } - } else if (pgpResult.success()) { - // see end of method - } else { - LogEntryParcel errorMsg = pgpResult.getLog().getLast(); - throw new Exception(getString(errorMsg.mType.getMsgId())); + } else if (pgpResult.success()) { + Intent result = new Intent(); + if (!cleartextSign) { + result.putExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE, pgpResult.getDetachedSignature()); } - - } finally { - is.close(); - os.close(); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + return result; + } else { + LogEntryParcel errorMsg = pgpResult.getLog().getLast(); + throw new Exception(getString(errorMsg.mType.getMsgId())); } - - Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); - return result; } catch (Exception e) { Log.d(Constants.TAG, "signImpl", e); Intent result = new Intent(); @@ -334,12 +300,29 @@ public class OpenPgpService extends RemoteService { new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException when closing InputStream", e); + } + } + if (os != null) { + try { + os.close(); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException when closing OutputStream", e); + } + } } } private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output, AccountSettings accSettings, boolean sign) { + InputStream is = null; + OutputStream os = null; try { boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); String originalFilename = data.getStringExtra(OpenPgpApi.EXTRA_ORIGINAL_FILENAME); @@ -365,99 +348,65 @@ public class OpenPgpService extends RemoteService { // build InputData and write into OutputStream // Get Input- and OutputStream from ParcelFileDescriptor - InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); - OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output); - try { - long inputLength = is.available(); - InputData inputData = new InputData(is, inputLength); - - PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( - this, new ProviderHelper(getContext()), null, inputData, os - ); - builder.setEnableAsciiArmorOutput(asciiArmor) - .setVersionHeader(PgpHelper.getVersionForHeader(this)) - .setCompressionId(accSettings.getCompression()) - .setSymmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm()) - .setEncryptionMasterKeyIds(keyIds) - .setFailOnMissingEncryptionKeyIds(true) - .setOriginalFilename(originalFilename) - .setAdditionalEncryptId(accSettings.getKeyId()); // add acc key for encryption - - if (sign) { - - // Find the appropriate subkey to sign with - long sigSubKeyId; - try { - CachedPublicKeyRing signingRing = - new ProviderHelper(this).getCachedPublicKeyRing(accSettings.getKeyId()); - sigSubKeyId = signingRing.getSecretSignId(); - } catch (PgpKeyNotFoundException e) { - // secret key that is set for this account is deleted? - // show account config again! - return getCreateAccountIntent(data, getAccountName(data)); - } - - String passphrase; - if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) { - passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); - } else { - passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), - accSettings.getKeyId(), sigSubKeyId); - } - if (passphrase == null) { - // get PendingIntent for passphrase input, add it to given params and return to client - return getPassphraseIntent(data, sigSubKeyId); - } - - byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH); - // carefully: only set if timestamp exists - Date nfcCreationDate = null; - long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1); - if (nfcCreationTimestamp != -1) { - nfcCreationDate = new Date(nfcCreationTimestamp); - } - - // sign and encrypt - builder.setSignatureHashAlgorithm(accSettings.getHashAlgorithm()) - .setSignatureMasterKeyId(accSettings.getKeyId()) - .setSignatureSubKeyId(sigSubKeyId) - .setSignaturePassphrase(passphrase) - .setNfcState(nfcSignedHash, nfcCreationDate); + is = new ParcelFileDescriptor.AutoCloseInputStream(input); + os = new ParcelFileDescriptor.AutoCloseOutputStream(output); + + long inputLength = is.available(); + InputData inputData = new InputData(is, inputLength, originalFilename); + + PgpSignEncryptInput pseInput = new PgpSignEncryptInput(); + pseInput.setEnableAsciiArmorOutput(asciiArmor) + .setVersionHeader(PgpHelper.getVersionForHeader(this)) + .setCompressionId(accSettings.getCompression()) + .setSymmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm()) + .setEncryptionMasterKeyIds(keyIds) + .setFailOnMissingEncryptionKeyIds(true) + .setAdditionalEncryptId(accSettings.getKeyId()); // add acc key for encryption + + if (sign) { + + byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH); + // carefully: only set if timestamp exists + Date nfcCreationDate = null; + long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1); + if (nfcCreationTimestamp != -1) { + nfcCreationDate = new Date(nfcCreationTimestamp); } - // execute PGP operation! - SignEncryptResult pgpResult = builder.build().execute(); - - if (pgpResult.isPending()) { - if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_PASSPHRASE) == - SignEncryptResult.RESULT_PENDING_PASSPHRASE) { - return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded()); - } else if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_NFC) == - SignEncryptResult.RESULT_PENDING_NFC) { - // return PendingIntent to execute NFC activity - // pass through the signature creation timestamp to be used again on second execution - // of PgpSignEncrypt when we have the signed hash! - data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime()); - return getNfcSignIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo()); - } else { - throw new PgpGeneralException( - "Encountered unhandled type of pending action not supported by API!"); - } - } else if (pgpResult.success()) { - // see end of method + // sign and encrypt + pseInput.setSignatureHashAlgorithm(accSettings.getHashAlgorithm()) + .setSignatureMasterKeyId(accSettings.getKeyId()) + .setNfcState(nfcSignedHash, nfcCreationDate); + } + + PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null); + + // execute PGP operation! + PgpSignEncryptResult pgpResult = op.execute(pseInput, inputData, os); + + if (pgpResult.isPending()) { + if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) == + PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) { + return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded()); + } else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) == + PgpSignEncryptResult.RESULT_PENDING_NFC) { + // return PendingIntent to execute NFC activity + // pass through the signature creation timestamp to be used again on second execution + // of PgpSignEncrypt when we have the signed hash! + data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime()); + return getNfcSignIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo()); } else { - LogEntryParcel errorMsg = pgpResult.getLog().getLast(); - throw new Exception(getString(errorMsg.mType.getMsgId())); + throw new PgpGeneralException( + "Encountered unhandled type of pending action not supported by API!"); } - - } finally { - is.close(); - os.close(); + } else if (pgpResult.success()) { + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + return result; + } else { + LogEntryParcel errorMsg = pgpResult.getLog().getLast(); + throw new Exception(getString(errorMsg.mType.getMsgId())); } - - Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); - return result; } catch (Exception e) { Log.d(Constants.TAG, "encryptAndSignImpl", e); Intent result = new Intent(); @@ -465,111 +414,129 @@ public class OpenPgpService extends RemoteService { new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException when closing InputStream", e); + } + } + if (os != null) { + try { + os.close(); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException when closing OutputStream", e); + } + } } } private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output, Set<Long> allowedKeyIds, boolean decryptMetadataOnly) { + InputStream is = null; + OutputStream os = null; try { // Get Input- and OutputStream from ParcelFileDescriptor - InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); + is = new ParcelFileDescriptor.AutoCloseInputStream(input); - OutputStream os; - if (decryptMetadataOnly) { + // output is optional, e.g., for verifying detached signatures + if (decryptMetadataOnly || output == null) { os = null; } else { os = new ParcelFileDescriptor.AutoCloseOutputStream(output); } - Intent result = new Intent(); - try { - String passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); - long inputLength = is.available(); - InputData inputData = new InputData(is, inputLength); - - PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( - this, new ProviderHelper(getContext()), null, inputData, os - ); - - byte[] nfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY); - - // allow only private keys associated with accounts of this app - // no support for symmetric encryption - builder.setPassphrase(passphrase) - .setAllowSymmetricDecryption(false) - .setAllowedKeyIds(allowedKeyIds) - .setDecryptMetadataOnly(decryptMetadataOnly) - .setNfcState(nfcDecryptedSessionKey); - - // TODO: currently does not support binary signed-only content - DecryptVerifyResult pgpResult = builder.build().execute(); - - if (pgpResult.isPending()) { - if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) == - DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) { - return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded()); - } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) == - DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) { - throw new PgpGeneralException( - "Decryption of symmetric content not supported by API!"); - } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) == - DecryptVerifyResult.RESULT_PENDING_NFC) { - return getNfcDecryptIntent( - data, pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey()); - } else { - throw new PgpGeneralException( - "Encountered unhandled type of pending action not supported by API!"); - } - } else if (pgpResult.success()) { - - OpenPgpSignatureResult signatureResult = pgpResult.getSignatureResult(); - if (signatureResult != null) { - result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult); - - if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 5) { - // SIGNATURE_KEY_REVOKED and SIGNATURE_KEY_EXPIRED have been added in version 5 - if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED - || signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED) { - signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_ERROR); - } + String passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); + long inputLength = is.available(); + InputData inputData = new InputData(is, inputLength); + + PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( + this, new ProviderHelper(getContext()), null, inputData, os + ); + + byte[] nfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY); + + byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE); + + // allow only private keys associated with accounts of this app + // no support for symmetric encryption + builder.setPassphrase(passphrase) + .setAllowSymmetricDecryption(false) + .setAllowedKeyIds(allowedKeyIds) + .setDecryptMetadataOnly(decryptMetadataOnly) + .setNfcState(nfcDecryptedSessionKey) + .setDetachedSignature(detachedSignature); + + DecryptVerifyResult pgpResult = builder.build().execute(); + + if (pgpResult.isPending()) { + if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) == + DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) { + return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded()); + } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) == + DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) { + throw new PgpGeneralException( + "Decryption of symmetric content not supported by API!"); + } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) == + DecryptVerifyResult.RESULT_PENDING_NFC) { + return getNfcDecryptIntent( + data, pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey()); + } else { + throw new PgpGeneralException( + "Encountered unhandled type of pending action not supported by API!"); + } + } else if (pgpResult.success()) { + Intent result = new Intent(); + + OpenPgpSignatureResult signatureResult = pgpResult.getSignatureResult(); + if (signatureResult != null) { + result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult); + + if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 5) { + // SIGNATURE_KEY_REVOKED and SIGNATURE_KEY_EXPIRED have been added in version 5 + if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED + || signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED) { + signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_ERROR); } + } - if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) { - // If signature is unknown we return an _additional_ PendingIntent - // to retrieve the missing key - Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class); - intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE); - intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, signatureResult.getKeyId()); - intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data); + if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) { + // If signature is unknown we return an _additional_ PendingIntent + // to retrieve the missing key + Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class); + intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE); + intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, signatureResult.getKeyId()); + intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data); - PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, - intent, - PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT); - result.putExtra(OpenPgpApi.RESULT_INTENT, pi); - } + result.putExtra(OpenPgpApi.RESULT_INTENT, pi); } + } - if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) >= 4) { - OpenPgpMetadata metadata = pgpResult.getDecryptMetadata(); - if (metadata != null) { - result.putExtra(OpenPgpApi.RESULT_METADATA, metadata); - } + if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) >= 4) { + OpenPgpMetadata metadata = pgpResult.getDecryptMetadata(); + if (metadata != null) { + result.putExtra(OpenPgpApi.RESULT_METADATA, metadata); } - } else { - LogEntryParcel errorMsg = pgpResult.getLog().getLast(); - throw new Exception(getString(errorMsg.mType.getMsgId())); } - } finally { - is.close(); - if (os != null) { - os.close(); + + String charset = pgpResult.getCharset(); + if (charset != null) { + result.putExtra(OpenPgpApi.RESULT_CHARSET, charset); } + + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + return result; + } else { + LogEntryParcel errorMsg = pgpResult.getLog().getLast(); + throw new Exception(getString(errorMsg.mType.getMsgId())); } - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); - return result; } catch (Exception e) { Log.d(Constants.TAG, "decryptAndVerifyImpl", e); Intent result = new Intent(); @@ -577,6 +544,21 @@ public class OpenPgpService extends RemoteService { new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException when closing InputStream", e); + } + } + if (os != null) { + try { + os.close(); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException when closing OutputStream", e); + } + } } } @@ -668,15 +650,16 @@ public class OpenPgpService extends RemoteService { // version code is required and needs to correspond to version code of service! // History of versions in org.openintents.openpgp.util.OpenPgpApi - // we support 3, 4, 5 + // we support 3, 4, 5, 6 if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 3 && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 4 - && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 5) { + && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 5 + && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 6) { Intent result = new Intent(); OpenPgpError error = new OpenPgpError (OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n" + "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n" - + "supported API versions: 3, 4"); + + "supported API versions: 3, 4, 5, 6"); result.putExtra(OpenPgpApi.RESULT_ERROR, error); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; @@ -706,42 +689,66 @@ public class OpenPgpService extends RemoteService { @Override public Intent execute(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) { - Intent errorResult = checkRequirements(data); - if (errorResult != null) { - return errorResult; - } + try { + Intent errorResult = checkRequirements(data); + if (errorResult != null) { + return errorResult; + } - String accName = getAccountName(data); - final AccountSettings accSettings = getAccSettings(accName); - if (accSettings == null) { - return getCreateAccountIntent(data, accName); - } + String accName = getAccountName(data); + final AccountSettings accSettings = getAccSettings(accName); + if (accSettings == null) { + return getCreateAccountIntent(data, accName); + } - String action = data.getAction(); - if (OpenPgpApi.ACTION_SIGN.equals(action)) { - return signImpl(data, input, output, accSettings); - } else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) { - return encryptAndSignImpl(data, input, output, accSettings, false); - } else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) { - return encryptAndSignImpl(data, input, output, accSettings, true); - } else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) { - String currentPkg = getCurrentCallingPackage(); - Set<Long> allowedKeyIds = - mProviderHelper.getAllKeyIdsForApp( - ApiAccounts.buildBaseUri(currentPkg)); - return decryptAndVerifyImpl(data, input, output, allowedKeyIds, false); - } else if (OpenPgpApi.ACTION_DECRYPT_METADATA.equals(action)) { - String currentPkg = getCurrentCallingPackage(); - Set<Long> allowedKeyIds = - mProviderHelper.getAllKeyIdsForApp( - ApiAccounts.buildBaseUri(currentPkg)); - return decryptAndVerifyImpl(data, input, output, allowedKeyIds, true); - } else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) { - return getKeyImpl(data); - } else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) { - return getKeyIdsImpl(data); - } else { - return null; + String action = data.getAction(); + if (OpenPgpApi.ACTION_CLEARTEXT_SIGN.equals(action)) { + return signImpl(data, input, output, accSettings, true); + } else if (OpenPgpApi.ACTION_SIGN.equals(action)) { + // DEPRECATED: same as ACTION_CLEARTEXT_SIGN + Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!"); + return signImpl(data, input, output, accSettings, true); + } else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(action)) { + return signImpl(data, input, output, accSettings, false); + } else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) { + return encryptAndSignImpl(data, input, output, accSettings, false); + } else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) { + return encryptAndSignImpl(data, input, output, accSettings, true); + } else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) { + String currentPkg = getCurrentCallingPackage(); + Set<Long> allowedKeyIds = + mProviderHelper.getAllKeyIdsForApp( + ApiAccounts.buildBaseUri(currentPkg)); + return decryptAndVerifyImpl(data, input, output, allowedKeyIds, false); + } else if (OpenPgpApi.ACTION_DECRYPT_METADATA.equals(action)) { + String currentPkg = getCurrentCallingPackage(); + Set<Long> allowedKeyIds = + mProviderHelper.getAllKeyIdsForApp( + ApiAccounts.buildBaseUri(currentPkg)); + return decryptAndVerifyImpl(data, input, output, allowedKeyIds, true); + } else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) { + return getKeyImpl(data); + } else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) { + return getKeyIdsImpl(data); + } else { + return null; + } + } finally { + // always close input and output file descriptors even in error cases + if (input != null) { + try { + input.close(); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException when closing input ParcelFileDescriptor", e); + } + } + if (output != null) { + try { + output.close(); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException when closing output ParcelFileDescriptor", e); + } + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java index e71b52ccd..672f59285 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java @@ -27,7 +27,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; import android.net.Uri; import android.os.Binder; -import android.text.TextUtils; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; @@ -216,9 +215,7 @@ public abstract class RemoteService extends Service { String[] callingPackages = getPackageManager().getPackagesForUid(uid); // is calling package allowed to use this service? - for (int i = 0; i < callingPackages.length; i++) { - String currentPkg = callingPackages[i]; - + for (String currentPkg : callingPackages) { if (isPackageAllowed(currentPkg)) { return true; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java index 0e9678980..4d6df24d2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java @@ -20,22 +20,21 @@ package org.sufficientlysecure.keychain.remote.ui; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.util.ActionBarHelper; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.SingletonResult; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.AccountSettings; +import org.sufficientlysecure.keychain.ui.BaseActivity; import org.sufficientlysecure.keychain.util.Log; -public class AccountSettingsActivity extends ActionBarActivity { +public class AccountSettingsActivity extends BaseActivity { private Uri mAccountUri; private AccountSettingsFragment mAccountSettingsFragment; @@ -45,17 +44,20 @@ public class AccountSettingsActivity extends ActionBarActivity { super.onCreate(savedInstanceState); // Inflate a "Done" custom action bar - ActionBarHelper.setOneButtonView(getSupportActionBar(), - R.string.api_settings_save, R.drawable.ic_action_done, + setFullScreenDialogDoneClose(R.string.api_settings_save, new View.OnClickListener() { @Override public void onClick(View v) { - // "Done" save(); } + }, + new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } }); - - setContentView(R.layout.api_account_settings_activity); + setTitle(null); mAccountSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById( R.id.api_account_settings_fragment); @@ -73,6 +75,11 @@ public class AccountSettingsActivity extends ActionBarActivity { } @Override + protected void initLayout() { + setContentView(R.layout.api_account_settings_activity); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.api_account_settings, menu); @@ -85,9 +92,6 @@ public class AccountSettingsActivity extends ActionBarActivity { case R.id.menu_account_settings_delete: deleteAccount(); return true; - case R.id.menu_account_settings_cancel: - finish(); - return true; } return super.onOptionsItemSelected(item); } @@ -125,9 +129,4 @@ public class AccountSettingsActivity extends ActionBarActivity { } } - @Override - public void onBackPressed() { - save(); - super.onBackPressed(); - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java index a3027f728..a5bc05ba8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java @@ -31,10 +31,10 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter; import org.sufficientlysecure.keychain.ui.widget.KeySpinner; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java index 56e3b22e2..407480c98 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -17,29 +17,54 @@ package org.sufficientlysecure.keychain.remote.ui; +import android.app.Activity; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import com.getbase.floatingactionbutton.FloatingActionButton; + +import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.AppSettings; -import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.BaseActivity; +import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment; import org.sufficientlysecure.keychain.util.Log; -public class AppSettingsActivity extends ActionBarActivity { +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class AppSettingsActivity extends BaseActivity { private Uri mAppUri; - private AppSettingsFragment mSettingsFragment; + private AppSettingsAllowedKeysListFragment mAllowedKeysFragment; + + private TextView mAppNameView; + private ImageView mAppIconView; + private TextView mPackageName; + private TextView mPackageSignature; + + private FloatingActionButton mStartFab; + + // deprecated API private AccountsListFragment mAccountsListFragment; + private TextView mAccountsLabel; + // model AppSettings mAppSettings; @@ -48,16 +73,27 @@ public class AppSettingsActivity extends ActionBarActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // let the actionbar look like Android's contact app - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setIcon(android.R.color.transparent); - actionBar.setHomeButtonEnabled(true); + mAccountsLabel = (TextView) findViewById(R.id.api_accounts_label); + mAppNameView = (TextView) findViewById(R.id.api_app_settings_app_name); + mAppIconView = (ImageView) findViewById(R.id.api_app_settings_app_icon); + mPackageName = (TextView) findViewById(R.id.api_app_settings_package_name); + mPackageSignature = (TextView) findViewById(R.id.api_app_settings_package_signature); + mStartFab = (FloatingActionButton) findViewById(R.id.fab); - setContentView(R.layout.api_app_settings_activity); + mStartFab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startApp(); + } + }); - mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( - R.id.api_app_settings_fragment); + setFullScreenDialogClose(new View.OnClickListener() { + @Override + public void onClick(View v) { + cancel(); + } + }); + setTitle(null); Intent intent = getIntent(); mAppUri = intent.getData(); @@ -71,6 +107,22 @@ public class AppSettingsActivity extends ActionBarActivity { } } + private void save() { + mAllowedKeysFragment.saveAllowedKeys(); + setResult(Activity.RESULT_OK); + finish(); + } + + private void cancel() { + setResult(Activity.RESULT_CANCELED); + finish(); + } + + @Override + protected void initLayout() { + setContentView(R.layout.api_app_settings_activity); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); @@ -81,16 +133,40 @@ public class AppSettingsActivity extends ActionBarActivity { @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.menu_api_settings_revoke: + case R.id.menu_api_save: { + save(); + return true; + } + case R.id.menu_api_settings_revoke: { revokeAccess(); return true; - case R.id.menu_api_settings_start: - startApp(); + } + case R.id.menu_api_settings_advanced: { + showAdvancedInfo(); return true; + } } return super.onOptionsItemSelected(item); } + private void showAdvancedInfo() { + String signature = null; + // advanced info: package signature SHA-256 + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(mAppSettings.getPackageSignature()); + byte[] digest = md.digest(); + signature = new String(Hex.encode(digest)); + } catch (NoSuchAlgorithmException e) { + Log.e(Constants.TAG, "Should not happen!", e); + } + + AdvancedAppSettingsDialogFragment dialogFragment = + AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), signature); + + dialogFragment.show(getSupportFragmentManager(), "advancedDialog"); + } + private void startApp() { Intent i; PackageManager manager = getPackageManager(); @@ -109,25 +185,31 @@ public class AppSettingsActivity extends ActionBarActivity { private void loadData(Bundle savedInstanceState, Uri appUri) { mAppSettings = new ProviderHelper(this).getApiAppSettings(appUri); - mSettingsFragment.setAppSettings(mAppSettings); + // get application name and icon from package manager String appName; - PackageManager pm = getPackageManager(); + Drawable appIcon = null; + PackageManager pm = getApplicationContext().getPackageManager(); try { ApplicationInfo ai = pm.getApplicationInfo(mAppSettings.getPackageName(), 0); + appName = (String) pm.getApplicationLabel(ai); + appIcon = pm.getApplicationIcon(ai); } catch (PackageManager.NameNotFoundException e) { // fallback appName = mAppSettings.getPackageName(); } - setTitle(appName); + mAppNameView.setText(appName); + mAppIconView.setImageDrawable(appIcon); Uri accountsUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ACCOUNTS).build(); Log.d(Constants.TAG, "accountsUri: " + accountsUri); - startListFragment(savedInstanceState, accountsUri); + Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build(); + Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri); + startListFragments(savedInstanceState, accountsUri, allowedKeysUri); } - private void startListFragment(Bundle savedInstanceState, Uri dataUri) { + private void startListFragments(Bundle savedInstanceState, Uri accountsUri, Uri allowedKeysUri) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -135,13 +217,23 @@ public class AppSettingsActivity extends ActionBarActivity { return; } - // Create an instance of the fragment - mAccountsListFragment = AccountsListFragment.newInstance(dataUri); + // show accounts only if available (deprecated API) + Cursor cursor = getContentResolver().query(accountsUri, null, null, null, null); + if (cursor.moveToFirst()) { + mAccountsLabel.setVisibility(View.VISIBLE); + mAccountsListFragment = AccountsListFragment.newInstance(accountsUri); + // Create an instance of the fragments + getSupportFragmentManager().beginTransaction() + .replace(R.id.api_accounts_list_fragment, mAccountsListFragment) + .commitAllowingStateLoss(); + } + // Create an instance of the fragments + mAllowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(allowedKeysUri); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() - .replace(R.id.api_accounts_list_fragment, mAccountsListFragment) + .replace(R.id.api_allowed_keys_list_fragment, mAllowedKeysFragment) .commitAllowingStateLoss(); // do it immediately! getSupportFragmentManager().executePendingTransactions(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java new file mode 100644 index 000000000..13b242a5e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2015 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.remote.ui; + +import android.content.Context; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; +import org.sufficientlysecure.keychain.ui.widget.FixedListView; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.Vector; + +public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks<Cursor> { + private static final String ARG_DATA_URI = "uri"; + + private SelectKeyCursorAdapter mAdapter; + private Set<Long> mSelectedMasterKeyIds; + private ProviderHelper mProviderHelper; + + private Uri mDataUri; + + /** + * Creates new instance of this fragment + */ + public static AppSettingsAllowedKeysListFragment newInstance(Uri dataUri) { + AppSettingsAllowedKeysListFragment frag = new AppSettingsAllowedKeysListFragment(); + Bundle args = new Bundle(); + + args.putParcelable(ARG_DATA_URI, dataUri); + + frag.setArguments(args); + + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mProviderHelper = new ProviderHelper(getActivity()); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View layout = super.onCreateView(inflater, container, + savedInstanceState); + ListView lv = (ListView) layout.findViewById(android.R.id.list); + ViewGroup parent = (ViewGroup) lv.getParent(); + + /* + * http://stackoverflow.com/a/15880684 + * Remove ListView and add FixedListView in its place. + * This is done here programatically to be still able to use the progressBar of ListFragment. + * + * We want FixedListView to be able to put this ListFragment inside a ScrollView + */ + int lvIndex = parent.indexOfChild(lv); + parent.removeViewAt(lvIndex); + FixedListView newLv = new FixedListView(getActivity()); + newLv.setId(android.R.id.list); + parent.addView(newLv, lvIndex, lv.getLayoutParams()); + return layout; + } + + /** + * Define Adapter and Loader on create of Activity + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mDataUri = getArguments().getParcelable(ARG_DATA_URI); + + getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + + // Give some text to display if there is no data. In a real + // application this would come from a resource. + setEmptyText(getString(R.string.list_empty)); + + mAdapter = new SecretKeyCursorAdapter(getActivity(), null, 0, getListView()); + + setListAdapter(mAdapter); + + // Start out with a progress indicator. + setListShown(false); + + mSelectedMasterKeyIds = mProviderHelper.getAllKeyIdsForApp(mDataUri); + Log.d(Constants.TAG, "allowed: " + mSelectedMasterKeyIds.toString()); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getLoaderManager().initLoader(0, null, this); + } + + /** + * Selects items based on master key ids in list view + * + * @param masterKeyIds + */ + private void preselectMasterKeyIds(Set<Long> masterKeyIds) { + for (int i = 0; i < getListView().getCount(); ++i) { + long listKeyId = mAdapter.getMasterKeyId(i); + for (long keyId : masterKeyIds) { + if (listKeyId == keyId) { + getListView().setItemChecked(i, true); + break; + } + } + } + } + + /** + * Returns all selected master key ids + * + * @return + */ + public Set<Long> getSelectedMasterKeyIds() { + // mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key + // ids! + Set<Long> keyIds = new HashSet<>(); + for (int i = 0; i < getListView().getCount(); ++i) { + if (getListView().isItemChecked(i)) { + keyIds.add(mAdapter.getMasterKeyId(i)); + } + } + + return keyIds; + } + + /** + * Returns all selected user ids + * + * @return + */ + public String[] getSelectedUserIds() { + Vector<String> userIds = new Vector<>(); + for (int i = 0; i < getListView().getCount(); ++i) { + if (getListView().isItemChecked(i)) { + userIds.add(mAdapter.getUserId(i)); + } + } + + // make empty array to not return null + String userIdArray[] = new String[0]; + return userIds.toArray(userIdArray); + } + + public void saveAllowedKeys() { + try { + mProviderHelper.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds()); + } catch (RemoteException | OperationApplicationException e) { + Log.e(Constants.TAG, "Problem saving allowed key ids!", e); + } + } + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); + + // These are the rows that we will retrieve. + String[] projection = new String[]{ + KeyRings._ID, + KeyRings.MASTER_KEY_ID, + KeyRings.USER_ID, + KeyRings.IS_EXPIRED, + KeyRings.IS_REVOKED, + KeyRings.HAS_ENCRYPT, + KeyRings.VERIFIED, + KeyRings.HAS_ANY_SECRET, + }; + + String inMasterKeyList = null; + if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.size() > 0) { + inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN ("; + Iterator iter = mSelectedMasterKeyIds.iterator(); + while (iter.hasNext()) { + inMasterKeyList += DatabaseUtils.sqlEscapeString("" + iter.next()); + if (iter.hasNext()) { + inMasterKeyList += ", "; + } + } + inMasterKeyList += ")"; + } + + String selection = KeyRings.HAS_ANY_SECRET + " != 0"; + + String orderBy = KeyRings.USER_ID + " ASC"; + if (inMasterKeyList != null) { + // sort by selected master keys + orderBy = inMasterKeyList + " DESC, " + orderBy; + } + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getActivity(), baseUri, projection, selection, null, orderBy); + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); + + // The list should now be shown. + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } + + // preselect given master keys + preselectMasterKeyIds(mSelectedMasterKeyIds); + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + // This is called when the last Cursor provided to onLoadFinished() + // above is about to be closed. We need to make sure we are no + // longer using it. + mAdapter.swapCursor(null); + } + + private class SecretKeyCursorAdapter extends SelectKeyCursorAdapter { + + public SecretKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) { + super(context, c, flags, listView); + } + + @Override + protected void initIndex(Cursor cursor) { + super.initIndex(cursor); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + super.bindView(view, context, cursor); + ViewHolderItem h = (ViewHolderItem) view.getTag(); + + // We care about the checkbox + h.selected.setVisibility(View.VISIBLE); + // the getListView works because this is not a static subclass! + h.selected.setChecked(getListView().isItemChecked(cursor.getPosition())); + + boolean enabled = false; + if ((Boolean) h.statusIcon.getTag()) { + h.statusIcon.setVisibility(View.GONE); + enabled = true; + } + + h.setEnabled(enabled); + } + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java index a6db02708..7beac8973 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java @@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.util.Log; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -public class AppSettingsFragment extends Fragment { +public class AppSettingsHeaderFragment extends Fragment { // model private AppSettings mAppSettings; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java deleted file mode 100644 index 11b0deb33..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java +++ /dev/null @@ -1,36 +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.remote.ui; - -import android.os.Bundle; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.DrawerActivity; - -public class AppsListActivity extends DrawerActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.api_apps_list_activity); - - activateDrawerNavigation(savedInstanceState); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java index 976ce20d6..2deb33a67 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2013-2015 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 @@ -48,82 +48,83 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.util.Log; public class AppsListFragment extends ListFragment implements - LoaderManager.LoaderCallbacks<Cursor> { + LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener { - // This is the Adapter being used to display the list's data. - RegisteredAppsAdapter mAdapter; + AppsAdapter mAdapter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - getListView().setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) { - String selectedPackageName = mAdapter.getItemPackageName(position); - boolean installed = mAdapter.getItemIsInstalled(position); - boolean registered = mAdapter.getItemIsRegistered(position); - - if (installed) { - if (registered) { - // edit app settings - Intent intent = new Intent(getActivity(), AppSettingsActivity.class); - intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName)); - startActivity(intent); - } else { - Intent i; - PackageManager manager = getActivity().getPackageManager(); - try { - i = manager.getLaunchIntentForPackage(selectedPackageName); - if (i == null) - throw new PackageManager.NameNotFoundException(); - // start like the Android launcher would do - i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - i.addCategory(Intent.CATEGORY_LAUNCHER); - startActivity(i); - } catch (PackageManager.NameNotFoundException e) { - Log.e(Constants.TAG, "startApp", e); - } - } - } else { - try { - startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse("market://details?id=" + selectedPackageName))); - } catch (ActivityNotFoundException anfe) { - startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse("http://play.google.com/store/apps/details?id=" + selectedPackageName))); - } - } - } - }); + getListView().setOnItemClickListener(this); - // Give some text to display if there is no data. In a real - // application this would come from a resource. - setEmptyText(getString(R.string.api_no_apps)); + // NOTE: No setEmptyText(), we always have the default entries // We have a menu item to show in action bar. setHasOptionsMenu(true); // Create an empty adapter we will use to display the loaded data. - mAdapter = new RegisteredAppsAdapter(getActivity(), null, 0); + mAdapter = new AppsAdapter(getActivity(), null, 0); setListAdapter(mAdapter); - // Loader is started in onResume! + // NOTE: Loader is started in onResume! } @Override public void onResume() { super.onResume(); - // after coming back from Google Play -> reload + + // Start out with a progress indicator. + setListShown(false); + + // After coming back from Google Play -> reload getLoaderManager().restartLoader(0, null, this); } + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + String selectedPackageName = mAdapter.getItemPackageName(position); + boolean installed = mAdapter.getItemIsInstalled(position); + boolean registered = mAdapter.getItemIsRegistered(position); + + if (installed) { + if (registered) { + // Edit app settings + Intent intent = new Intent(getActivity(), AppSettingsActivity.class); + intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName)); + startActivity(intent); + } else { + Intent i; + PackageManager manager = getActivity().getPackageManager(); + try { + i = manager.getLaunchIntentForPackage(selectedPackageName); + if (i == null) { + throw new PackageManager.NameNotFoundException(); + } + // Start like the Android launcher would do + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + i.addCategory(Intent.CATEGORY_LAUNCHER); + startActivity(i); + } catch (PackageManager.NameNotFoundException e) { + Log.e(Constants.TAG, "startApp", e); + } + } + } else { + try { + startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse("market://details?id=" + selectedPackageName))); + } catch (ActivityNotFoundException anfe) { + startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse("https://play.google.com/store/apps/details?id=" + selectedPackageName))); + } + } + } + private static final String TEMP_COLUMN_NAME = "NAME"; private static final String TEMP_COLUMN_INSTALLED = "INSTALLED"; private static final String TEMP_COLUMN_REGISTERED = "REGISTERED"; private static final String TEMP_COLUMN_ICON_RES_ID = "ICON_RES_ID"; - // These are the Contacts rows that we will retrieve. static final String[] PROJECTION = new String[]{ ApiApps._ID, // 0 ApiApps.PACKAGE_NAME, // 1 @@ -149,106 +150,17 @@ public class AppsListFragment extends ListFragment implements // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, + return new AppsLoader(getActivity(), baseUri, PROJECTION, null, null, ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC"); } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - MatrixCursor availableAppsCursor = new MatrixCursor(new String[]{ - ApiApps._ID, - ApiApps.PACKAGE_NAME, - TEMP_COLUMN_NAME, - TEMP_COLUMN_INSTALLED, - TEMP_COLUMN_REGISTERED, - TEMP_COLUMN_ICON_RES_ID - }); - // NOTE: SORT ascending by package name, this is REQUIRED for CursorJoiner! - // Drawables taken from projects res/drawables-xxhdpi/ic_launcher.png - availableAppsCursor.addRow(new Object[]{1, "com.fsck.k9", "K-9 Mail", 0, 0, R.drawable.apps_k9}); - availableAppsCursor.addRow(new Object[]{1, "com.zeapo.pwdstore", "Password Store", 0, 0, R.drawable.apps_password_store}); - availableAppsCursor.addRow(new Object[]{1, "eu.siacs.conversations", "Conversations (Instant Messaging)", 0, 0, R.drawable.apps_conversations}); - - MatrixCursor mergedCursor = new MatrixCursor(new String[]{ - ApiApps._ID, - ApiApps.PACKAGE_NAME, - TEMP_COLUMN_NAME, - TEMP_COLUMN_INSTALLED, - TEMP_COLUMN_REGISTERED, - TEMP_COLUMN_ICON_RES_ID - }); - - CursorJoiner joiner = new CursorJoiner( - availableAppsCursor, - new String[]{ApiApps.PACKAGE_NAME}, - data, - new String[]{ApiApps.PACKAGE_NAME}); - for (CursorJoiner.Result joinerResult : joiner) { - switch (joinerResult) { - case LEFT: { - // handle case where a row in availableAppsCursor is unique - String packageName = availableAppsCursor.getString(INDEX_PACKAGE_NAME); - - mergedCursor.addRow(new Object[]{ - 1, // no need for unique _ID - packageName, - availableAppsCursor.getString(INDEX_NAME), - isInstalled(packageName), - 0, - availableAppsCursor.getInt(INDEX_ICON_RES_ID) - }); - break; - } - case RIGHT: { - // handle case where a row in data is unique - String packageName = data.getString(INDEX_PACKAGE_NAME); - - mergedCursor.addRow(new Object[]{ - 1, // no need for unique _ID - packageName, - null, - isInstalled(packageName), - 1, // registered! - R.drawable.ic_launcher // icon is retrieved later - }); - break; - } - case BOTH: { - // handle case where a row with the same key is in both cursors - String packageName = data.getString(INDEX_PACKAGE_NAME); - - String name; - if (isInstalled(packageName) == 1) { - name = data.getString(INDEX_NAME); - } else { - // if not installed take name from available apps list - name = availableAppsCursor.getString(INDEX_NAME); - } - - mergedCursor.addRow(new Object[]{ - 1, // no need for unique _ID - packageName, - name, - isInstalled(packageName), - 1, // registered! - R.drawable.ic_launcher // icon is retrieved later - }); - break; - } - } - } - // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) - mAdapter.swapCursor(mergedCursor); - } + mAdapter.swapCursor(data); - private int isInstalled(String packageName) { - try { - getActivity().getPackageManager().getApplicationInfo(packageName, 0); - return 1; - } catch (final PackageManager.NameNotFoundException e) { - return 0; - } + // The list should now be shown. + setListShown(true); } public void onLoaderReset(Loader<Cursor> loader) { @@ -258,12 +170,127 @@ public class AppsListFragment extends ListFragment implements mAdapter.swapCursor(null); } - private class RegisteredAppsAdapter extends CursorAdapter { + /** + * Besides the queried cursor with all registered apps, this loader also returns non-installed + * proposed apps using a MatrixCursor. + */ + private static class AppsLoader extends CursorLoader { + + public AppsLoader(Context context) { + super(context); + } + + public AppsLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + super(context, uri, projection, selection, selectionArgs, sortOrder); + } + + @Override + public Cursor loadInBackground() { + // Load registered apps from content provider + Cursor data = super.loadInBackground(); + + MatrixCursor availableAppsCursor = new MatrixCursor(new String[]{ + ApiApps._ID, + ApiApps.PACKAGE_NAME, + TEMP_COLUMN_NAME, + TEMP_COLUMN_INSTALLED, + TEMP_COLUMN_REGISTERED, + TEMP_COLUMN_ICON_RES_ID + }); + // NOTE: SORT ascending by package name, this is REQUIRED for CursorJoiner! + // Drawables taken from projects res/drawables-xxhdpi/ic_launcher.png + availableAppsCursor.addRow(new Object[]{1, "com.fsck.k9", "K-9 Mail", 0, 0, R.drawable.apps_k9}); + availableAppsCursor.addRow(new Object[]{1, "com.zeapo.pwdstore", "Password Store", 0, 0, R.drawable.apps_password_store}); + availableAppsCursor.addRow(new Object[]{1, "eu.siacs.conversations", "Conversations (Instant Messaging)", 0, 0, R.drawable.apps_conversations}); + + MatrixCursor mergedCursor = new MatrixCursor(new String[]{ + ApiApps._ID, + ApiApps.PACKAGE_NAME, + TEMP_COLUMN_NAME, + TEMP_COLUMN_INSTALLED, + TEMP_COLUMN_REGISTERED, + TEMP_COLUMN_ICON_RES_ID + }); + + CursorJoiner joiner = new CursorJoiner( + availableAppsCursor, + new String[]{ApiApps.PACKAGE_NAME}, + data, + new String[]{ApiApps.PACKAGE_NAME}); + for (CursorJoiner.Result joinerResult : joiner) { + switch (joinerResult) { + case LEFT: { + // handle case where a row in availableAppsCursor is unique + String packageName = availableAppsCursor.getString(INDEX_PACKAGE_NAME); + + mergedCursor.addRow(new Object[]{ + 1, // no need for unique _ID + packageName, + availableAppsCursor.getString(INDEX_NAME), + isInstalled(packageName), + 0, + availableAppsCursor.getInt(INDEX_ICON_RES_ID) + }); + break; + } + case RIGHT: { + // handle case where a row in data is unique + String packageName = data.getString(INDEX_PACKAGE_NAME); + + mergedCursor.addRow(new Object[]{ + 1, // no need for unique _ID + packageName, + null, + isInstalled(packageName), + 1, // registered! + R.drawable.ic_launcher // icon is retrieved later + }); + break; + } + case BOTH: { + // handle case where a row with the same key is in both cursors + String packageName = data.getString(INDEX_PACKAGE_NAME); + + String name; + if (isInstalled(packageName) == 1) { + name = data.getString(INDEX_NAME); + } else { + // if not installed take name from available apps list + name = availableAppsCursor.getString(INDEX_NAME); + } + + mergedCursor.addRow(new Object[]{ + 1, // no need for unique _ID + packageName, + name, + isInstalled(packageName), + 1, // registered! + R.drawable.ic_launcher // icon is retrieved later + }); + break; + } + } + } + + return mergedCursor; + } + + private int isInstalled(String packageName) { + try { + getContext().getPackageManager().getApplicationInfo(packageName, 0); + return 1; + } catch (final PackageManager.NameNotFoundException e) { + return 0; + } + } + } + + private class AppsAdapter extends CursorAdapter { private LayoutInflater mInflater; private PackageManager mPM; - public RegisteredAppsAdapter(Context context, Cursor c, int flags) { + public AppsAdapter(Context context, Cursor c, int flags) { super(context, c, flags); mInflater = LayoutInflater.from(context); @@ -273,44 +300,23 @@ public class AppsListFragment extends ListFragment implements /** * Similar to CursorAdapter.getItemId(). * Required to build Uris for api apps, which are not based on row ids - * - * @param position - * @return */ public String getItemPackageName(int position) { - if (mDataValid && mCursor != null) { - if (mCursor.moveToPosition(position)) { - return mCursor.getString(INDEX_PACKAGE_NAME); - } else { - return null; - } + if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) { + return mCursor.getString(INDEX_PACKAGE_NAME); } else { return null; } } public boolean getItemIsInstalled(int position) { - if (mDataValid && mCursor != null) { - if (mCursor.moveToPosition(position)) { - return (mCursor.getInt(INDEX_INSTALLED) == 1); - } else { - return false; - } - } else { - return false; - } + return mDataValid && mCursor != null + && mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_INSTALLED) == 1); } public boolean getItemIsRegistered(int position) { - if (mDataValid && mCursor != null) { - if (mCursor.moveToPosition(position)) { - return (mCursor.getInt(INDEX_REGISTERED) == 1); - } else { - return false; - } - } else { - return false; - } + return mDataValid && mCursor != null + && mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_REGISTERED) == 1); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java index d7b723eb0..2c5c78161 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -22,7 +22,6 @@ import android.graphics.Color; import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; @@ -39,14 +38,14 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.ui.BaseActivity; import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; -import org.sufficientlysecure.keychain.ui.util.ActionBarHelper; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; -public class RemoteServiceActivity extends ActionBarActivity { +public class RemoteServiceActivity extends BaseActivity { public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER"; public static final String ACTION_CREATE_ACCOUNT = Constants.INTENT_PREFIX @@ -76,7 +75,7 @@ public class RemoteServiceActivity extends ActionBarActivity { public static final String EXTRA_ERROR_MESSAGE = "error_message"; // register view - private AppSettingsFragment mAppSettingsFragment; + private AppSettingsHeaderFragment mAppSettingsHeaderFragment; // create acc view private AccountSettingsFragment mAccSettingsFragment; // select pub keys view @@ -96,235 +95,250 @@ public class RemoteServiceActivity extends ActionBarActivity { handleActions(getIntent(), savedInstanceState); } + @Override + protected void initLayout() { + // done in handleActions() + } + protected void handleActions(Intent intent, Bundle savedInstanceState) { String action = intent.getAction(); final Bundle extras = intent.getExtras(); - if (ACTION_REGISTER.equals(action)) { - final String packageName = extras.getString(EXTRA_PACKAGE_NAME); - final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); - Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName); + switch (action) { + case ACTION_REGISTER: { + final String packageName = extras.getString(EXTRA_PACKAGE_NAME); + final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); + Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName); - setContentView(R.layout.api_remote_register_app); + setContentView(R.layout.api_remote_register_app); + initToolbar(); - mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( - R.id.api_app_settings_fragment); + mAppSettingsHeaderFragment = (AppSettingsHeaderFragment) getSupportFragmentManager().findFragmentById( + R.id.api_app_settings_fragment); - AppSettings settings = new AppSettings(packageName, packageSignature); - mAppSettingsFragment.setAppSettings(settings); + AppSettings settings = new AppSettings(packageName, packageSignature); + mAppSettingsHeaderFragment.setAppSettings(settings); - // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setTwoButtonView(getSupportActionBar(), - R.string.api_register_allow, R.drawable.ic_action_done, - new View.OnClickListener() { - @Override - public void onClick(View v) { - // Allow + // Inflate a "Done"/"Cancel" custom action bar view + setFullScreenDialogTwoButtons( + R.string.api_register_allow, R.drawable.ic_check_white_24dp, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Allow - mProviderHelper.insertApiApp(mAppSettingsFragment.getAppSettings()); + mProviderHelper.insertApiApp(mAppSettingsHeaderFragment.getAppSettings()); - // give data through for new service call - Intent resultData = extras.getParcelable(EXTRA_DATA); - RemoteServiceActivity.this.setResult(RESULT_OK, resultData); - RemoteServiceActivity.this.finish(); - } - }, R.string.api_register_disallow, R.drawable.ic_action_cancel, - new View.OnClickListener() { - @Override - public void onClick(View v) { - // Disallow - RemoteServiceActivity.this.setResult(RESULT_CANCELED); - RemoteServiceActivity.this.finish(); + // give data through for new service call + Intent resultData = extras.getParcelable(EXTRA_DATA); + RemoteServiceActivity.this.setResult(RESULT_OK, resultData); + RemoteServiceActivity.this.finish(); + } + }, R.string.api_register_disallow, R.drawable.ic_close_white_24dp, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Disallow + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + RemoteServiceActivity.this.finish(); + } } - } - ); - } else if (ACTION_CREATE_ACCOUNT.equals(action)) { - final String packageName = extras.getString(EXTRA_PACKAGE_NAME); - final String accName = extras.getString(EXTRA_ACC_NAME); + ); + break; + } + case ACTION_CREATE_ACCOUNT: { + final String packageName = extras.getString(EXTRA_PACKAGE_NAME); + final String accName = extras.getString(EXTRA_ACC_NAME); - setContentView(R.layout.api_remote_create_account); + setContentView(R.layout.api_remote_create_account); + initToolbar(); - mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById( - R.id.api_account_settings_fragment); + mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_account_settings_fragment); - TextView text = (TextView) findViewById(R.id.api_remote_create_account_text); + TextView text = (TextView) findViewById(R.id.api_remote_create_account_text); - // update existing? - Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(packageName, accName); - AccountSettings settings = mProviderHelper.getApiAccountSettings(uri); - if (settings == null) { - // create new account - settings = new AccountSettings(accName); - mUpdateExistingAccount = false; + // update existing? + Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(packageName, accName); + AccountSettings settings = mProviderHelper.getApiAccountSettings(uri); + if (settings == null) { + // create new account + settings = new AccountSettings(accName); + mUpdateExistingAccount = false; - text.setText(R.string.api_create_account_text); - } else { - // update existing account - mUpdateExistingAccount = true; + text.setText(R.string.api_create_account_text); + } else { + // update existing account + mUpdateExistingAccount = true; - text.setText(R.string.api_update_account_text); - } - mAccSettingsFragment.setAccSettings(settings); - - // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setTwoButtonView(getSupportActionBar(), - R.string.api_settings_save, R.drawable.ic_action_done, - new View.OnClickListener() { - @Override - public void onClick(View v) { - // Save - - // user needs to select a key if it has explicitly requested (None is only allowed for new accounts) - if (mUpdateExistingAccount && mAccSettingsFragment.getAccSettings().getKeyId() == Constants.key.none) { - Notify.showNotify(RemoteServiceActivity.this, getString(R.string.api_register_error_select_key), Notify.Style.ERROR); - } else { - if (mUpdateExistingAccount) { - Uri baseUri = KeychainContract.ApiAccounts.buildBaseUri(packageName); - Uri accountUri = baseUri.buildUpon().appendEncodedPath(accName).build(); - mProviderHelper.updateApiAccount( - accountUri, - mAccSettingsFragment.getAccSettings()); + text.setText(R.string.api_update_account_text); + } + mAccSettingsFragment.setAccSettings(settings); + + // Inflate a "Done"/"Cancel" custom action bar view + setFullScreenDialogDoneClose(R.string.api_settings_save, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Save + + // user needs to select a key if it has explicitly requested (None is only allowed for new accounts) + if (mUpdateExistingAccount && mAccSettingsFragment.getAccSettings().getKeyId() == Constants.key.none) { + Notify.showNotify(RemoteServiceActivity.this, getString(R.string.api_register_error_select_key), Notify.Style.ERROR); } else { - mProviderHelper.insertApiAccount( - KeychainContract.ApiAccounts.buildBaseUri(packageName), - mAccSettingsFragment.getAccSettings()); + if (mUpdateExistingAccount) { + Uri baseUri = KeychainContract.ApiAccounts.buildBaseUri(packageName); + Uri accountUri = baseUri.buildUpon().appendEncodedPath(accName).build(); + mProviderHelper.updateApiAccount( + accountUri, + mAccSettingsFragment.getAccSettings()); + } else { + mProviderHelper.insertApiAccount( + KeychainContract.ApiAccounts.buildBaseUri(packageName), + mAccSettingsFragment.getAccSettings()); + } + + // give data through for new service call + Intent resultData = extras.getParcelable(EXTRA_DATA); + RemoteServiceActivity.this.setResult(RESULT_OK, resultData); + RemoteServiceActivity.this.finish(); } - - // give data through for new service call - Intent resultData = extras.getParcelable(EXTRA_DATA); - RemoteServiceActivity.this.setResult(RESULT_OK, resultData); + } + }, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Cancel + RemoteServiceActivity.this.setResult(RESULT_CANCELED); RemoteServiceActivity.this.finish(); } - } - }, R.string.api_settings_cancel, R.drawable.ic_action_cancel, - new View.OnClickListener() { - @Override - public void onClick(View v) { - // Cancel - RemoteServiceActivity.this.setResult(RESULT_CANCELED); - RemoteServiceActivity.this.finish(); - } + }); + + break; + } + case ACTION_SELECT_PUB_KEYS: { + long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); + boolean noUserIdsCheck = intent.getBooleanExtra(EXTRA_NO_USER_IDS_CHECK, true); + ArrayList<String> missingUserIds = intent + .getStringArrayListExtra(EXTRA_MISSING_USER_IDS); + ArrayList<String> dublicateUserIds = intent + .getStringArrayListExtra(EXTRA_DUPLICATE_USER_IDS); + + SpannableStringBuilder ssb = new SpannableStringBuilder(); + final SpannableString textIntro = new SpannableString( + noUserIdsCheck ? getString(R.string.api_select_pub_keys_text_no_user_ids) + : getString(R.string.api_select_pub_keys_text) + ); + textIntro.setSpan(new StyleSpan(Typeface.BOLD), 0, textIntro.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(textIntro); + + if (missingUserIds != null && missingUserIds.size() > 0) { + ssb.append("\n\n"); + ssb.append(getString(R.string.api_select_pub_keys_missing_text)); + ssb.append("\n"); + for (String userId : missingUserIds) { + SpannableString ss = new SpannableString(userId + "\n"); + ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(ss); } - ); - - } else if (ACTION_SELECT_PUB_KEYS.equals(action)) { - long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); - boolean noUserIdsCheck = intent.getBooleanExtra(EXTRA_NO_USER_IDS_CHECK, true); - ArrayList<String> missingUserIds = intent - .getStringArrayListExtra(EXTRA_MISSING_USER_IDS); - ArrayList<String> dublicateUserIds = intent - .getStringArrayListExtra(EXTRA_DUPLICATE_USER_IDS); - - SpannableStringBuilder ssb = new SpannableStringBuilder(); - final SpannableString textIntro = new SpannableString( - noUserIdsCheck ? getString(R.string.api_select_pub_keys_text_no_user_ids) - : getString(R.string.api_select_pub_keys_text) - ); - textIntro.setSpan(new StyleSpan(Typeface.BOLD), 0, textIntro.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - ssb.append(textIntro); - - if (missingUserIds != null && missingUserIds.size() > 0) { - ssb.append("\n\n"); - ssb.append(getString(R.string.api_select_pub_keys_missing_text)); - ssb.append("\n"); - for (String userId : missingUserIds) { - SpannableString ss = new SpannableString(userId + "\n"); - ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - ssb.append(ss); } - } - if (dublicateUserIds != null && dublicateUserIds.size() > 0) { - ssb.append("\n\n"); - ssb.append(getString(R.string.api_select_pub_keys_dublicates_text)); - ssb.append("\n"); - for (String userId : dublicateUserIds) { - SpannableString ss = new SpannableString(userId + "\n"); - ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - ssb.append(ss); + if (dublicateUserIds != null && dublicateUserIds.size() > 0) { + ssb.append("\n\n"); + ssb.append(getString(R.string.api_select_pub_keys_dublicates_text)); + ssb.append("\n"); + for (String userId : dublicateUserIds) { + SpannableString ss = new SpannableString(userId + "\n"); + ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(ss); + } } - } - // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setTwoButtonView(getSupportActionBar(), - R.string.btn_okay, R.drawable.ic_action_done, - new View.OnClickListener() { - @Override - public void onClick(View v) { - // add key ids to params Bundle for new request - Intent resultData = extras.getParcelable(EXTRA_DATA); - resultData.putExtra(OpenPgpApi.EXTRA_KEY_IDS, - mSelectFragment.getSelectedMasterKeyIds()); - - RemoteServiceActivity.this.setResult(RESULT_OK, resultData); - RemoteServiceActivity.this.finish(); - } - }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() { - @Override - public void onClick(View v) { - // cancel - RemoteServiceActivity.this.setResult(RESULT_CANCELED); - RemoteServiceActivity.this.finish(); - } - } - ); + setContentView(R.layout.api_remote_select_pub_keys); + initToolbar(); - setContentView(R.layout.api_remote_select_pub_keys); + // Inflate a "Done"/"Cancel" custom action bar view + setFullScreenDialogDoneClose(R.string.btn_okay, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // add key ids to params Bundle for new request + Intent resultData = extras.getParcelable(EXTRA_DATA); + resultData.putExtra(OpenPgpApi.EXTRA_KEY_IDS, + mSelectFragment.getSelectedMasterKeyIds()); - // set text on view - TextView textView = (TextView) findViewById(R.id.api_select_pub_keys_text); - textView.setText(ssb, TextView.BufferType.SPANNABLE); + RemoteServiceActivity.this.setResult(RESULT_OK, resultData); + RemoteServiceActivity.this.finish(); + } + }, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // cancel + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + RemoteServiceActivity.this.finish(); + } + }); + + // set text on view + TextView textView = (TextView) findViewById(R.id.api_select_pub_keys_text); + textView.setText(ssb, TextView.BufferType.SPANNABLE); + + /* Load select pub keys fragment */ + // Check that the activity is using the layout version with + // the fragment_container FrameLayout + if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) { + + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; + } - /* Load select pub keys fragment */ - // Check that the activity is using the layout version with - // the fragment_container FrameLayout - if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) { + // Create an instance of the fragment + mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds); - // However, if we're being restored from a previous state, - // then we don't need to do anything and should return or else - // we could end up with overlapping fragments. - if (savedInstanceState != null) { - return; + // Add the fragment to the 'fragment_container' FrameLayout + getSupportFragmentManager().beginTransaction() + .add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit(); } - - // Create an instance of the fragment - mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds); - - // Add the fragment to the 'fragment_container' FrameLayout - getSupportFragmentManager().beginTransaction() - .add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit(); + break; } - } else if (ACTION_ERROR_MESSAGE.equals(action)) { - String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE); + case ACTION_ERROR_MESSAGE: { + String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE); - Spannable redErrorMessage = new SpannableString(errorMessage); - redErrorMessage.setSpan(new ForegroundColorSpan(Color.RED), 0, errorMessage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + Spannable redErrorMessage = new SpannableString(errorMessage); + redErrorMessage.setSpan(new ForegroundColorSpan(Color.RED), 0, errorMessage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - // Inflate a "Done" custom action bar view - ActionBarHelper.setOneButtonView(getSupportActionBar(), - R.string.btn_okay, R.drawable.ic_action_done, - new View.OnClickListener() { + setContentView(R.layout.api_remote_error_message); + initToolbar(); - @Override - public void onClick(View v) { - RemoteServiceActivity.this.setResult(RESULT_CANCELED); - RemoteServiceActivity.this.finish(); - } - } - ); + // Inflate a "Done" custom action bar view + setFullScreenDialogClose( + new View.OnClickListener() { - setContentView(R.layout.api_remote_error_message); + @Override + public void onClick(View v) { + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + RemoteServiceActivity.this.finish(); + } + } + ); - // set text on view - TextView textView = (TextView) findViewById(R.id.api_app_error_message_text); - textView.setText(redErrorMessage); - } else { - Log.e(Constants.TAG, "Action does not exist!"); - setResult(RESULT_CANCELED); - finish(); + // set text on view + TextView textView = (TextView) findViewById(R.id.api_app_error_message_text); + textView.setText(redErrorMessage); + break; + } + default: + Log.e(Constants.TAG, "Action does not exist!"); + setResult(RESULT_CANCELED); + finish(); + break; } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java index dd9c0d769..f0dbf0820 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -34,7 +34,7 @@ public class CertifyActionsParcel implements Parcelable { final public long mMasterKeyId; public CertifyLevel mLevel; - public ArrayList<CertifyAction> mCertifyActions = new ArrayList<CertifyAction>(); + public ArrayList<CertifyAction> mCertifyActions = new ArrayList<>(); public CertifyActionsParcel(long masterKeyId) { mMasterKeyId = masterKeyId; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index 447f413ef..7688b9252 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -53,7 +53,7 @@ public class ContactSyncAdapterService extends Service { // new Handler.Callback() { // @Override // public boolean handleMessage(Message msg) { -// Bundle data = msg.getData(); +// Bundle data = msg.getInputData(); // switch (msg.arg1) { // case KeychainIntentServiceHandler.MESSAGE_OKAY: // Log.d(Constants.TAG, "Syncing... Done."); 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 dc9592710..d95701458 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -38,7 +38,10 @@ import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.CertifyOperation; import org.sufficientlysecure.keychain.operations.DeleteOperation; +import org.sufficientlysecure.keychain.operations.EditKeyOperation; import org.sufficientlysecure.keychain.operations.ImportExportOperation; +import org.sufficientlysecure.keychain.operations.PromoteKeyOperation; +import org.sufficientlysecure.keychain.operations.SignEncryptOperation; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; @@ -47,30 +50,20 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; -import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; -import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; -import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; -import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -79,8 +72,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -113,6 +104,8 @@ public class KeychainIntentService extends IntentService implements Progressable public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING"; + public static final String ACTION_PROMOTE_KEYRING = Constants.INTENT_PREFIX + "PROMOTE_KEYRING"; + public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING"; public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING"; @@ -135,24 +128,11 @@ public class KeychainIntentService extends IntentService implements Progressable // possible targets: public static final int IO_BYTES = 1; public static final int IO_URI = 2; - public static final int IO_URIS = 3; - - public static final String SELECTED_URI = "selected_uri"; // encrypt - public static final String ENCRYPT_SIGNATURE_MASTER_ID = "secret_key_id"; - public static final String ENCRYPT_SIGNATURE_KEY_PASSPHRASE = "secret_key_passphrase"; - public static final String ENCRYPT_SIGNATURE_NFC_TIMESTAMP = "signature_nfc_timestamp"; - public static final String ENCRYPT_SIGNATURE_NFC_HASH = "signature_nfc_hash"; - public static final String ENCRYPT_USE_ASCII_ARMOR = "use_ascii_armor"; - public static final String ENCRYPT_ENCRYPTION_KEYS_IDS = "encryption_keys_ids"; - public static final String ENCRYPT_COMPRESSION_ID = "compression_id"; - public static final String ENCRYPT_MESSAGE_BYTES = "message_bytes"; public static final String ENCRYPT_DECRYPT_INPUT_URI = "input_uri"; - public static final String ENCRYPT_INPUT_URIS = "input_uris"; public static final String ENCRYPT_DECRYPT_OUTPUT_URI = "output_uri"; - public static final String ENCRYPT_OUTPUT_URIS = "output_uris"; - public static final String ENCRYPT_SYMMETRIC_PASSPHRASE = "passphrase"; + public static final String SIGN_ENCRYPT_PARCEL = "sign_encrypt_parcel"; // decrypt/verify public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes"; @@ -188,6 +168,10 @@ public class KeychainIntentService extends IntentService implements Progressable // certify key public static final String CERTIFY_PARCEL = "certify_parcel"; + // promote key + public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id"; + public static final String PROMOTE_TYPE = "promote_type"; + // consolidate public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery"; @@ -196,9 +180,6 @@ public class KeychainIntentService extends IntentService implements Progressable * possible data keys as result send over messenger */ - // encrypt - public static final String RESULT_BYTES = "encrypted_data"; - // decrypt/verify public static final String RESULT_DECRYPTED_BYTES = "decrypted_data"; @@ -251,461 +232,341 @@ public class KeychainIntentService extends IntentService implements Progressable String action = intent.getAction(); // executeServiceMethod action from extra bundle - if (ACTION_CERTIFY_KEYRING.equals(action)) { - - // Input - CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL); - String keyServerUri = data.getString(UPLOAD_KEY_SERVER); + switch (action) { + case ACTION_CERTIFY_KEYRING: { - // Operation - CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled); - CertifyResult result = op.certify(parcel, keyServerUri); + // Input + CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL); + String keyServerUri = data.getString(UPLOAD_KEY_SERVER); - // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + // Operation + CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled); + CertifyResult result = op.certify(parcel, keyServerUri); - } else if (ACTION_CONSOLIDATE.equals(action)) { + // Result + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - // Operation - ConsolidateResult result; - if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) { - result = new ProviderHelper(this).consolidateDatabaseStep2(this); - } else { - result = new ProviderHelper(this).consolidateDatabaseStep1(this); + break; } + case ACTION_CONSOLIDATE: { - // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + // Operation + ConsolidateResult result; + if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) { + result = new ProviderHelper(this).consolidateDatabaseStep2(this); + } else { + result = new ProviderHelper(this).consolidateDatabaseStep1(this); + } - } else if (ACTION_DECRYPT_METADATA.equals(action)) { + // Result + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - try { + break; + } + case ACTION_DECRYPT_METADATA: { + + try { /* Input */ - String passphrase = data.getString(DECRYPT_PASSPHRASE); - byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY); + String passphrase = data.getString(DECRYPT_PASSPHRASE); + byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY); - InputData inputData = createDecryptInputData(data); + InputData inputData = createDecryptInputData(data); /* Operation */ - Bundle resultData = new Bundle(); - - // verifyText and decrypt returning additional resultData values for the - // verification of signatures - PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( - this, new ProviderHelper(this), this, inputData, null - ); - builder.setAllowSymmetricDecryption(true) - .setPassphrase(passphrase) - .setDecryptMetadataOnly(true) - .setNfcState(nfcDecryptedSessionKey); + Bundle resultData = new Bundle(); - DecryptVerifyResult decryptVerifyResult = builder.build().execute(); + // verifyText and decrypt returning additional resultData values for the + // verification of signatures + PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( + this, new ProviderHelper(this), this, inputData, null + ); + builder.setAllowSymmetricDecryption(true) + .setPassphrase(passphrase) + .setDecryptMetadataOnly(true) + .setNfcState(nfcDecryptedSessionKey); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, decryptVerifyResult); - } catch (Exception e) { - sendErrorToHandler(e); - } + DecryptVerifyResult decryptVerifyResult = builder.build().execute(); - } else if (ACTION_VERIFY_KEYBASE_PROOF.equals(action)) { - try { - Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF))); - setProgress(R.string.keybase_message_fetching_data, 0, 100); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, decryptVerifyResult); + } catch (Exception e) { + sendErrorToHandler(e); + } - Prover prover = Prover.findProverFor(proof); + break; + } + case ACTION_VERIFY_KEYBASE_PROOF: { - if (prover == null) { - sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof.getPrettyName()); - return; - } + try { + Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF))); + setProgress(R.string.keybase_message_fetching_data, 0, 100); - if (!prover.fetchProofData()) { - sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence)); - return; - } - String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT); - if (!prover.checkFingerprint(requiredFingerprint)) { - sendProofError(getString(R.string.keybase_key_mismatch)); - return; - } + Prover prover = Prover.findProverFor(proof); - String domain = prover.dnsTxtCheckRequired(); - if (domain != null) { - DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT)); - if (dnsQuery == null) { - sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure)); + if (prover == null) { + sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof.getPrettyName()); return; } - Record[] records = dnsQuery.getAnswers(); - List<List<byte[]>> extents = new ArrayList<List<byte[]>>(); - for (Record r : records) { - Data d = r.getPayload(); - if (d instanceof TXT) { - extents.add(((TXT) d).getExtents()); - } + + if (!prover.fetchProofData()) { + sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence)); + return; } - if (!prover.checkDnsTxt(extents)) { - sendProofError(prover.getLog(), null); + String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT); + if (!prover.checkFingerprint(requiredFingerprint)) { + sendProofError(getString(R.string.keybase_key_mismatch)); return; } - } - byte[] messageBytes = prover.getPgpMessage().getBytes(); - if (prover.rawMessageCheckRequired()) { - InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(messageBytes)); - if (!prover.checkRawMessageBytes(messageByteStream)) { - sendProofError(prover.getLog(), null); - return; + String domain = prover.dnsTxtCheckRequired(); + if (domain != null) { + DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT)); + if (dnsQuery == null) { + sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure)); + return; + } + Record[] records = dnsQuery.getAnswers(); + List<List<byte[]>> extents = new ArrayList<List<byte[]>>(); + for (Record r : records) { + Data d = r.getPayload(); + if (d instanceof TXT) { + extents.add(((TXT) d).getExtents()); + } + } + if (!prover.checkDnsTxt(extents)) { + sendProofError(prover.getLog(), null); + return; + } } - } - // kind of awkward, but this whole class wants to pull bytes out of “data” - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); - data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, messageBytes); + byte[] messageBytes = prover.getPgpMessage().getBytes(); + if (prover.rawMessageCheckRequired()) { + InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(messageBytes)); + if (!prover.checkRawMessageBytes(messageByteStream)) { + sendProofError(prover.getLog(), null); + return; + } + } + + // kind of awkward, but this whole class wants to pull bytes out of “data” + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); + data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, messageBytes); - InputData inputData = createDecryptInputData(data); - OutputStream outStream = createCryptOutputStream(data); + InputData inputData = createDecryptInputData(data); + OutputStream outStream = createCryptOutputStream(data); + + PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( + this, new ProviderHelper(this), this, + inputData, outStream + ); + builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint); - PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( - this, new ProviderHelper(this), this, - inputData, outStream - ); - builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint); + DecryptVerifyResult decryptVerifyResult = builder.build().execute(); + outStream.close(); - DecryptVerifyResult decryptVerifyResult = builder.build().execute(); - outStream.close(); + if (!decryptVerifyResult.success()) { + OperationLog log = decryptVerifyResult.getLog(); + OperationResult.LogEntryParcel lastEntry = null; + for (OperationResult.LogEntryParcel entry : log) { + lastEntry = entry; + } + sendProofError(getString(lastEntry.mType.getMsgId())); + return; + } - if (!decryptVerifyResult.success()) { - OperationLog log = decryptVerifyResult.getLog(); - OperationResult.LogEntryParcel lastEntry = null; - for (OperationResult.LogEntryParcel entry : log) { - lastEntry = entry; + if (!prover.validate(outStream.toString())) { + sendProofError(getString(R.string.keybase_message_payload_mismatch)); + return; } - sendProofError(getString(lastEntry.mType.getMsgId())); - return; - } - if (!prover.validate(outStream.toString())) { - sendProofError(getString(R.string.keybase_message_payload_mismatch)); - return; - } + Bundle resultData = new Bundle(); + resultData.putString(KeychainIntentServiceHandler.DATA_MESSAGE, "OK"); - Bundle resultData = new Bundle(); - resultData.putString(KeychainIntentServiceHandler.DATA_MESSAGE, "OK"); + // these help the handler construct a useful human-readable message + resultData.putString(KeychainIntentServiceHandler.KEYBASE_PROOF_URL, prover.getProofUrl()); + resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl()); + resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_LABEL, prover.getPresenceLabel()); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); + } catch (Exception e) { + sendErrorToHandler(e); + } - // these help the handler construct a useful human-readable message - resultData.putString(KeychainIntentServiceHandler.KEYBASE_PROOF_URL, prover.getProofUrl()); - resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl()); - resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_LABEL, prover.getPresenceLabel()); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); - } catch (Exception e) { - sendErrorToHandler(e); + break; } + case ACTION_DECRYPT_VERIFY: { - } else if (ACTION_DECRYPT_VERIFY.equals(action)) { - - try { + try { /* Input */ - String passphrase = data.getString(DECRYPT_PASSPHRASE); - byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY); + String passphrase = data.getString(DECRYPT_PASSPHRASE); + byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY); - InputData inputData = createDecryptInputData(data); - OutputStream outStream = createCryptOutputStream(data); + InputData inputData = createDecryptInputData(data); + OutputStream outStream = createCryptOutputStream(data); /* Operation */ - Bundle resultData = new Bundle(); + Bundle resultData = new Bundle(); - // verifyText and decrypt returning additional resultData values for the - // verification of signatures - PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( - this, new ProviderHelper(this), this, - inputData, outStream - ); - builder.setAllowSymmetricDecryption(true) - .setPassphrase(passphrase) - .setNfcState(nfcDecryptedSessionKey); + // verifyText and decrypt returning additional resultData values for the + // verification of signatures + PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( + this, new ProviderHelper(this), this, + inputData, outStream + ); + builder.setAllowSymmetricDecryption(true) + .setPassphrase(passphrase) + .setNfcState(nfcDecryptedSessionKey); - DecryptVerifyResult decryptVerifyResult = builder.build().execute(); + DecryptVerifyResult decryptVerifyResult = builder.build().execute(); - outStream.close(); + outStream.close(); - resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult); + resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult); /* Output */ - finalizeDecryptOutputStream(data, resultData, outStream); + finalizeDecryptOutputStream(data, resultData, outStream); - Log.logDebugBundle(resultData, "resultData"); + Log.logDebugBundle(resultData, "resultData"); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); - } catch (Exception e) { - sendErrorToHandler(e); - } + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); + } catch (Exception e) { + sendErrorToHandler(e); + } - } else if (ACTION_DELETE.equals(action)) { + break; + } + case ACTION_DELETE: { - // Input - long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST); - boolean isSecret = data.getBoolean(DELETE_IS_SECRET); + // Input + long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST); + boolean isSecret = data.getBoolean(DELETE_IS_SECRET); - // Operation - DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this); - DeleteResult result = op.execute(masterKeyIds, isSecret); + // Operation + DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this); + DeleteResult result = op.execute(masterKeyIds, isSecret); - // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + // Result + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - } else if (ACTION_EDIT_KEYRING.equals(action)) { + break; + } + case ACTION_EDIT_KEYRING: { - try { - /* Input */ + // Input SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL); - if (saveParcel == null) { - Log.e(Constants.TAG, "bug: missing save_keyring_parcel in data!"); - return; - } - - /* Operation */ - PgpKeyOperation keyOperations = - new PgpKeyOperation(new ProgressScaler(this, 10, 60, 100), mActionCanceled); - EditKeyResult modifyResult; - - if (saveParcel.mMasterKeyId != null) { - String passphrase = data.getString(EDIT_KEYRING_PASSPHRASE); - CanonicalizedSecretKeyRing secRing = - new ProviderHelper(this).getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId); - - modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase); - } else { - modifyResult = keyOperations.createSecretKeyRing(saveParcel); - } - - // If the edit operation didn't succeed, exit here - if (!modifyResult.success()) { - // always return SaveKeyringResult, so create one out of the EditKeyResult - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, modifyResult); - return; - } - - UncachedKeyRing ring = modifyResult.getRing(); + String passphrase = data.getString(EDIT_KEYRING_PASSPHRASE); - // Check if the action was cancelled - if (mActionCanceled.get()) { - OperationLog log = modifyResult.getLog(); - // If it wasn't added before, add log entry - if (!modifyResult.cancelled()) { - log.add(LogType.MSG_OPERATION_CANCELLED, 0); - } - // If so, just stop without saving - modifyResult = new EditKeyResult( - EditKeyResult.RESULT_CANCELLED, log, null); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, modifyResult); - return; - } + // Operation + EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled); + EditKeyResult result = op.execute(saveParcel, passphrase); - // Save the keyring. The ProviderHelper is initialized with the previous log - SaveKeyringResult saveResult = new ProviderHelper(this, modifyResult.getLog()) - .saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100)); + // Result + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - // If the edit operation didn't succeed, exit here - if (!saveResult.success()) { - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult); - return; - } + break; + } + case ACTION_PROMOTE_KEYRING: { - // cache new passphrase - if (saveParcel.mNewPassphrase != null) { - PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(), ring.getMasterKeyId(), - saveParcel.mNewPassphrase, ring.getPublicKey().getPrimaryUserIdWithFallback()); - } + // Input + long keyRingId = data.getInt(EXPORT_KEY_RING_MASTER_KEY_ID); - setProgress(R.string.progress_done, 100, 100); + // Operation + PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled); + PromoteKeyResult result = op.execute(keyRingId); - // make sure new data is synced into contacts - ContactSyncAdapterService.requestSync(); + // Result + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - /* Output */ - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult); - } catch (Exception e) { - sendErrorToHandler(e); + break; } + case ACTION_EXPORT_KEYRING: { - } else if (ACTION_EXPORT_KEYRING.equals(action)) { + // Input + boolean exportSecret = data.getBoolean(EXPORT_SECRET, false); + String outputFile = data.getString(EXPORT_FILENAME); + Uri outputUri = data.getParcelable(EXPORT_URI); - // Input - boolean exportSecret = data.getBoolean(EXPORT_SECRET, false); - String outputFile = data.getString(EXPORT_FILENAME); - Uri outputUri = data.getParcelable(EXPORT_URI); + boolean exportAll = data.getBoolean(EXPORT_ALL); + long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID); - boolean exportAll = data.getBoolean(EXPORT_ALL); - long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID); + // Operation + ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this); + ExportResult result; + if (outputFile != null) { + result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile); + } else { + result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri); + } + // Result + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - // Operation - ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this); - ExportResult result; - if (outputFile != null) { - result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile); - } else { - result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri); + break; } - - // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - - } else if (ACTION_IMPORT_KEYRING.equals(action)) { - - try { + case ACTION_IMPORT_KEYRING: { // Input String keyServer = data.getString(IMPORT_KEY_SERVER); - Iterator<ParcelableKeyRing> entries; - int numEntries; - if (data.containsKey(IMPORT_KEY_LIST)) { - // get entries from intent - ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST); - entries = list.iterator(); - numEntries = list.size(); - } else { - // get entries from cached file - ParcelableFileCache<ParcelableKeyRing> cache = - new ParcelableFileCache<ParcelableKeyRing>(this, "key_import.pcl"); - IteratorWithSize<ParcelableKeyRing> it = cache.readCache(); - entries = it; - numEntries = it.getSize(); - } + ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST); + ParcelableFileCache<ParcelableKeyRing> cache = + new ParcelableFileCache<>(this, "key_import.pcl"); // Operation ImportExportOperation importExportOperation = new ImportExportOperation( this, providerHelper, this, mActionCanceled); - ImportKeyResult result = importExportOperation.importKeyRings(entries, numEntries, keyServer); - - // Special: consolidate on secret key import (cannot be cancelled!) - if (result.mSecret > 0) { - // TODO move this into the import operation - providerHelper.consolidateDatabaseStep1(this); - } - - // Special: make sure new data is synced into contacts - ContactSyncAdapterService.requestSync(); + // Either list or cache must be null, no guarantees otherwise. + ImportKeyResult result = list != null + ? importExportOperation.importKeyRings(list, keyServer) + : importExportOperation.importKeyRings(cache, keyServer); // Result sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - } catch (Exception e) { - sendErrorToHandler(e); - } - } else if (ACTION_SIGN_ENCRYPT.equals(action)) { - - try { - /* Input */ - int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); - Bundle resultData = new Bundle(); - - long sigMasterKeyId = data.getLong(ENCRYPT_SIGNATURE_MASTER_ID); - String sigKeyPassphrase = data.getString(ENCRYPT_SIGNATURE_KEY_PASSPHRASE); - - byte[] nfcHash = data.getByteArray(ENCRYPT_SIGNATURE_NFC_HASH); - Date nfcTimestamp = (Date) data.getSerializable(ENCRYPT_SIGNATURE_NFC_TIMESTAMP); - - String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE); - - boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR); - long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS); - int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID); - int urisCount = data.containsKey(ENCRYPT_INPUT_URIS) ? data.getParcelableArrayList(ENCRYPT_INPUT_URIS).size() : 1; - for (int i = 0; i < urisCount; i++) { - data.putInt(SELECTED_URI, i); - InputData inputData = createEncryptInputData(data); - OutputStream outStream = createCryptOutputStream(data); - String originalFilename = getOriginalFilename(data); - - /* Operation */ - PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( - this, new ProviderHelper(this), this, inputData, outStream - ); - builder.setEnableAsciiArmorOutput(useAsciiArmor) - .setVersionHeader(PgpHelper.getVersionForHeader(this)) - .setCompressionId(compressionId) - .setSymmetricEncryptionAlgorithm( - Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) - .setEncryptionMasterKeyIds(encryptionKeyIds) - .setSymmetricPassphrase(symmetricPassphrase) - .setOriginalFilename(originalFilename); - - try { - - // Find the appropriate subkey to sign with - CachedPublicKeyRing signingRing = - new ProviderHelper(this).getCachedPublicKeyRing(sigMasterKeyId); - long sigSubKeyId = signingRing.getSecretSignId(); - - // Set signature settings - builder.setSignatureMasterKeyId(sigMasterKeyId) - .setSignatureSubKeyId(sigSubKeyId) - .setSignaturePassphrase(sigKeyPassphrase) - .setSignatureHashAlgorithm( - Preferences.getPreferences(this).getDefaultHashAlgorithm()) - .setAdditionalEncryptId(sigMasterKeyId); - if (nfcHash != null && nfcTimestamp != null) { - builder.setNfcState(nfcHash, nfcTimestamp); - } - - } catch (PgpKeyNotFoundException e) { - // encrypt-only - // TODO Just silently drop the requested signature? Shouldn't we throw here? - } - - // this assumes that the bytes are cleartext (valid for current implementation!) - if (source == IO_BYTES) { - builder.setCleartextInput(true); - } - - SignEncryptResult result = builder.build().execute(); - resultData.putParcelable(SignEncryptResult.EXTRA_RESULT, result); - - outStream.close(); + break; - /* Output */ + } + case ACTION_SIGN_ENCRYPT: { - finalizeEncryptOutputStream(data, resultData, outStream); + // Input + SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL); - } + // Operation + SignEncryptOperation op = new SignEncryptOperation( + this, new ProviderHelper(this), this, mActionCanceled); + SignEncryptResult result = op.execute(inputParcel); - Log.logDebugBundle(resultData, "resultData"); + // Result + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); - } catch (Exception e) { - sendErrorToHandler(e); + break; } + case ACTION_UPLOAD_KEYRING: { + try { - } else if (ACTION_UPLOAD_KEYRING.equals(action)) { - - try { + /* Input */ + String keyServer = data.getString(UPLOAD_KEY_SERVER); + // and dataUri! - /* Input */ - String keyServer = data.getString(UPLOAD_KEY_SERVER); - // and dataUri! + /* Operation */ + HkpKeyserver server = new HkpKeyserver(keyServer); - /* Operation */ - HkpKeyserver server = new HkpKeyserver(keyServer); + CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri); + ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this); - CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri); - ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this); + try { + importExportOperation.uploadKeyRingToServer(server, keyring); + } catch (Keyserver.AddKeyException e) { + throw new PgpGeneralException("Unable to export key to selected server"); + } - try { - importExportOperation.uploadKeyRingToServer(server, keyring); - } catch (Keyserver.AddKeyException e) { - throw new PgpGeneralException("Unable to export key to selected server"); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); + } catch (Exception e) { + sendErrorToHandler(e); } - - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); - } catch (Exception e) { - sendErrorToHandler(e); + break; } } - } private void sendProofError(List<String> log, String label) { @@ -810,10 +671,6 @@ public class KeychainIntentService extends IntentService implements Progressable return createCryptInputData(data, DECRYPT_CIPHERTEXT_BYTES); } - private InputData createEncryptInputData(Bundle data) throws IOException, PgpGeneralException { - return createCryptInputData(data, ENCRYPT_MESSAGE_BYTES); - } - private InputData createCryptInputData(Bundle data, String bytesName) throws PgpGeneralException, IOException { int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); switch (source) { @@ -827,33 +684,6 @@ public class KeychainIntentService extends IntentService implements Progressable // InputStream return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0)); - case IO_URIS: - providerUri = data.<Uri>getParcelableArrayList(ENCRYPT_INPUT_URIS).get(data.getInt(SELECTED_URI)); - - // InputStream - return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0)); - - default: - throw new PgpGeneralException("No target choosen!"); - } - } - - private String getOriginalFilename(Bundle data) throws PgpGeneralException, FileNotFoundException { - int target = data.getInt(TARGET); - switch (target) { - case IO_BYTES: - return ""; - - case IO_URI: - Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_INPUT_URI); - - return FileHelper.getFilename(this, providerUri); - - case IO_URIS: - providerUri = data.<Uri>getParcelableArrayList(ENCRYPT_INPUT_URIS).get(data.getInt(SELECTED_URI)); - - return FileHelper.getFilename(this, providerUri); - default: throw new PgpGeneralException("No target choosen!"); } @@ -870,20 +700,11 @@ public class KeychainIntentService extends IntentService implements Progressable return getContentResolver().openOutputStream(providerUri); - case IO_URIS: - providerUri = data.<Uri>getParcelableArrayList(ENCRYPT_OUTPUT_URIS).get(data.getInt(SELECTED_URI)); - - return getContentResolver().openOutputStream(providerUri); - default: throw new PgpGeneralException("No target choosen!"); } } - private void finalizeEncryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) { - finalizeCryptOutputStream(data, resultData, outStream, RESULT_BYTES); - } - private void finalizeDecryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) { finalizeCryptOutputStream(data, resultData, outStream, RESULT_DECRYPTED_BYTES); } @@ -896,7 +717,6 @@ public class KeychainIntentService extends IntentService implements Progressable resultData.putByteArray(bytesName, output); break; case IO_URI: - case IO_URIS: // nothing, output was written, just send okay and verification bundle break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java index fc65757f5..ceb0a2d2b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java @@ -27,8 +27,8 @@ import android.support.v4.app.FragmentManager; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; -import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.Log; public class KeychainIntentServiceHandler extends Handler { @@ -136,6 +136,7 @@ public class KeychainIntentServiceHandler extends Handler { case MESSAGE_PREVENT_CANCEL: mProgressDialogFragment.setPreventCancel(true); + break; default: Log.e(Constants.TAG, "unknown handler message!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 869d2e71b..57881f8ee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -39,12 +39,11 @@ import android.support.v4.util.LongSparseArray; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.util.Date; @@ -103,7 +102,7 @@ public class PassphraseCacheService extends Service { private BroadcastReceiver mIntentReceiver; - private LongSparseArray<CachedPassphrase> mPassphraseCache = new LongSparseArray<CachedPassphrase>(); + private LongSparseArray<CachedPassphrase> mPassphraseCache = new LongSparseArray<>(); Context mContext; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index c4be467e4..e2d0c03c9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -21,6 +21,8 @@ package org.sufficientlysecure.keychain.service; import android.os.Parcel; import android.os.Parcelable; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; + import java.io.Serializable; import java.util.ArrayList; @@ -46,9 +48,10 @@ public class SaveKeyringParcel implements Parcelable { // the key fingerprint, for safety. MUST be null for a new key. public byte[] mFingerprint; - public String mNewPassphrase; + public ChangeUnlockParcel mNewUnlock; public ArrayList<String> mAddUserIds; + public ArrayList<WrappedUserAttribute> mAddUserAttribute; public ArrayList<SubkeyAdd> mAddSubKeys; public ArrayList<SubkeyChange> mChangeSubKeys; @@ -56,7 +59,6 @@ public class SaveKeyringParcel implements Parcelable { public ArrayList<String> mRevokeUserIds; public ArrayList<Long> mRevokeSubKeys; - public ArrayList<Long> mStripSubKeys; public SaveKeyringParcel() { reset(); @@ -69,14 +71,31 @@ public class SaveKeyringParcel implements Parcelable { } public void reset() { - mNewPassphrase = null; - mAddUserIds = new ArrayList<String>(); - mAddSubKeys = new ArrayList<SubkeyAdd>(); + mNewUnlock = null; + mAddUserIds = new ArrayList<>(); + mAddUserAttribute = new ArrayList<>(); + mAddSubKeys = new ArrayList<>(); mChangePrimaryUserId = null; - mChangeSubKeys = new ArrayList<SubkeyChange>(); - mRevokeUserIds = new ArrayList<String>(); - mRevokeSubKeys = new ArrayList<Long>(); - mStripSubKeys = new ArrayList<Long>(); + mChangeSubKeys = new ArrayList<>(); + mRevokeUserIds = new ArrayList<>(); + mRevokeSubKeys = new ArrayList<>(); + } + + /** Returns true iff this parcel does not contain any operations which require a passphrase. */ + public boolean isRestrictedOnly() { + if (mNewUnlock != null || !mAddUserIds.isEmpty() || !mAddUserAttribute.isEmpty() + || !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeSubKeys .isEmpty() + || !mRevokeSubKeys.isEmpty()) { + return false; + } + + for (SubkeyChange change : mChangeSubKeys) { + if (change.mRecertify || change.mFlags != null || change.mExpiry != null) { + return false; + } + } + + return true; } // performance gain for using Parcelable here would probably be negligible, @@ -109,26 +128,53 @@ public class SaveKeyringParcel implements Parcelable { } public static class SubkeyChange implements Serializable { - public long mKeyId; + public final long mKeyId; public Integer mFlags; // this is a long unix timestamp, in seconds (NOT MILLISECONDS!) public Long mExpiry; + // if this flag is true, the key will be recertified even if all above + // values are no-ops + public boolean mRecertify; + // if this flag is true, the subkey should be changed to a stripped key + public boolean mDummyStrip; + // if this is non-null, the subkey will be changed to a divert-to-card + // key for the given serial number + public byte[] mDummyDivert; public SubkeyChange(long keyId) { mKeyId = keyId; } + public SubkeyChange(long keyId, boolean recertify) { + mKeyId = keyId; + mRecertify = recertify; + } + public SubkeyChange(long keyId, Integer flags, Long expiry) { mKeyId = keyId; mFlags = flags; mExpiry = expiry; } + public SubkeyChange(long keyId, boolean dummyStrip, byte[] dummyDivert) { + this(keyId, null, null); + + // these flags are mutually exclusive! + if (dummyStrip && dummyDivert != null) { + throw new AssertionError( + "cannot set strip and divert flags at the same time - this is a bug!"); + } + mDummyStrip = dummyStrip; + mDummyDivert = dummyDivert; + } + @Override public String toString() { String out = "mKeyId: " + mKeyId + ", "; out += "mFlags: " + mFlags + ", "; - out += "mExpiry: " + mExpiry; + out += "mExpiry: " + mExpiry + ", "; + out += "mDummyStrip: " + mDummyStrip + ", "; + out += "mDummyDivert: [" + (mDummyDivert == null ? 0 : mDummyDivert.length) + " bytes]"; return out; } @@ -159,9 +205,10 @@ public class SaveKeyringParcel implements Parcelable { mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; mFingerprint = source.createByteArray(); - mNewPassphrase = source.readString(); + mNewUnlock = source.readParcelable(getClass().getClassLoader()); mAddUserIds = source.createStringArrayList(); + mAddUserAttribute = (ArrayList<WrappedUserAttribute>) source.readSerializable(); mAddSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable(); mChangeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable(); @@ -169,7 +216,6 @@ public class SaveKeyringParcel implements Parcelable { mRevokeUserIds = source.createStringArrayList(); mRevokeSubKeys = (ArrayList<Long>) source.readSerializable(); - mStripSubKeys = (ArrayList<Long>) source.readSerializable(); } @Override @@ -180,9 +226,11 @@ public class SaveKeyringParcel implements Parcelable { } destination.writeByteArray(mFingerprint); - destination.writeString(mNewPassphrase); + // yes, null values are ok for parcelables + destination.writeParcelable(mNewUnlock, 0); destination.writeStringList(mAddUserIds); + destination.writeSerializable(mAddUserAttribute); destination.writeSerializable(mAddSubKeys); destination.writeSerializable(mChangeSubKeys); @@ -190,7 +238,6 @@ public class SaveKeyringParcel implements Parcelable { destination.writeStringList(mRevokeUserIds); destination.writeSerializable(mRevokeSubKeys); - destination.writeSerializable(mStripSubKeys); } public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() { @@ -211,14 +258,14 @@ public class SaveKeyringParcel implements Parcelable { @Override public String toString() { String out = "mMasterKeyId: " + mMasterKeyId + "\n"; - out += "mNewPassphrase: " + mNewPassphrase + "\n"; + out += "mNewUnlock: " + mNewUnlock + "\n"; out += "mAddUserIds: " + mAddUserIds + "\n"; + out += "mAddUserAttribute: " + mAddUserAttribute + "\n"; out += "mAddSubKeys: " + mAddSubKeys + "\n"; out += "mChangeSubKeys: " + mChangeSubKeys + "\n"; out += "mChangePrimaryUserId: " + mChangePrimaryUserId + "\n"; out += "mRevokeUserIds: " + mRevokeUserIds + "\n"; - out += "mRevokeSubKeys: " + mRevokeSubKeys + "\n"; - out += "mStripSubKeys: " + mStripSubKeys; + out += "mRevokeSubKeys: " + mRevokeSubKeys; return out; } @@ -238,4 +285,67 @@ public class SaveKeyringParcel implements Parcelable { // BRAINPOOL_P256, BRAINPOOL_P384, BRAINPOOL_P512 } + /** This subclass contains information on how the passphrase should be changed. + * + * If no changes are to be made, this class should NOT be used! + * + * At this point, there must be *exactly one* non-null value here, which specifies the type + * of unlocking mechanism to use. + * + */ + public static class ChangeUnlockParcel implements Parcelable { + + // The new passphrase to use + public final String mNewPassphrase; + // A new pin to use. Must only contain [0-9]+ + public final String mNewPin; + + public ChangeUnlockParcel(String newPassphrase) { + this(newPassphrase, null); + } + public ChangeUnlockParcel(String newPassphrase, String newPin) { + if (newPassphrase == null && newPin == null) { + throw new RuntimeException("Cannot set both passphrase and pin. THIS IS A BUG!"); + } + if (newPin != null && !newPin.matches("[0-9]+")) { + throw new RuntimeException("Pin must be numeric digits only. THIS IS A BUG!"); + } + mNewPassphrase = newPassphrase; + mNewPin = newPin; + } + + public ChangeUnlockParcel(Parcel source) { + mNewPassphrase = source.readString(); + mNewPin = source.readString(); + } + + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeString(mNewPassphrase); + destination.writeString(mNewPin); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<ChangeUnlockParcel> CREATOR = new Creator<ChangeUnlockParcel>() { + public ChangeUnlockParcel createFromParcel(final Parcel source) { + return new ChangeUnlockParcel(source); + } + + public ChangeUnlockParcel[] newArray(final int size) { + return new ChangeUnlockParcel[size]; + } + }; + + public String toString() { + return mNewPassphrase != null + ? ("passphrase (" + mNewPassphrase + ")") + : ("pin (" + mNewPin + ")"); + } + + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java new file mode 100644 index 000000000..e6c2542a2 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarActivity; +import android.support.v7.widget.Toolbar; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; + +/** + * Setups Toolbar + */ +public abstract class BaseActivity extends ActionBarActivity { + protected Toolbar mToolbar; + protected View mStatusBar; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initLayout(); + initToolbar(); + } + + protected abstract void initLayout(); + + protected void initToolbar() { + mToolbar = (Toolbar) findViewById(R.id.toolbar); + if (mToolbar != null) { + setSupportActionBar(mToolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + mStatusBar = findViewById(R.id.status_bar); + } + + protected void setActionBarIcon(int iconRes) { + mToolbar.setNavigationIcon(iconRes); + } + + /** + * Inflate custom design to look like a full screen dialog, as specified in Material Design Guidelines + * see http://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs + */ + protected void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener, + View.OnClickListener cancelOnClickListener) { + setActionBarIcon(R.drawable.ic_close_white_24dp); + + // Inflate the custom action bar view + final LayoutInflater inflater = (LayoutInflater) getSupportActionBar().getThemedContext() + .getSystemService(Activity.LAYOUT_INFLATER_SERVICE); + final View customActionBarView = inflater.inflate(R.layout.full_screen_dialog, null); + + TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.full_screen_dialog_done_text)); + firstTextView.setText(doneText); + customActionBarView.findViewById(R.id.full_screen_dialog_done).setOnClickListener( + doneOnClickListener); + + getSupportActionBar().setDisplayShowCustomEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(true); + getSupportActionBar().setCustomView(customActionBarView, new ActionBar.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT, + Gravity.END)); + mToolbar.setNavigationOnClickListener(cancelOnClickListener); + } + + /** + * Close button only + */ + protected void setFullScreenDialogClose(View.OnClickListener cancelOnClickListener) { + setActionBarIcon(R.drawable.ic_close_white_24dp); + getSupportActionBar().setDisplayShowTitleEnabled(true); + mToolbar.setNavigationOnClickListener(cancelOnClickListener); + } + + /** + * Inflate custom design with two buttons using drawables. + * This does not conform to the Material Design Guidelines, but we deviate here as this is used + * to indicate "Allow access"/"Disallow access" to the API, which must be clearly indicated + */ + protected void setFullScreenDialogTwoButtons(int firstText, int firstDrawableId, View.OnClickListener firstOnClickListener, + int secondText, int secondDrawableId, View.OnClickListener secondOnClickListener) { + + // Inflate the custom action bar view + final LayoutInflater inflater = (LayoutInflater) getSupportActionBar().getThemedContext() + .getSystemService(Activity.LAYOUT_INFLATER_SERVICE); + final View customActionBarView = inflater.inflate( + R.layout.full_screen_dialog_2, null); + + TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)); + firstTextView.setText(firstText); + firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0); + customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener( + firstOnClickListener); + TextView secondTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text)); + secondTextView.setText(secondText); + secondTextView.setCompoundDrawablesWithIntrinsicBounds(secondDrawableId, 0, 0, 0); + customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener( + secondOnClickListener); + + // Show the custom action bar view and hide the normal Home icon and title. + getSupportActionBar().setDisplayShowTitleEnabled(false); + getSupportActionBar().setDisplayShowHomeEnabled(false); + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + getSupportActionBar().setDisplayShowCustomEnabled(true); + getSupportActionBar().setCustomView(customActionBarView, new ActionBar.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvancedActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java index f49f8e7cf..777288e69 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvancedActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java @@ -1,6 +1,5 @@ /* - * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> - * Copyright (C) 2013 Bahtiar 'kalkin' Gadimov + * Copyright (C) 2015 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 @@ -18,57 +17,47 @@ package org.sufficientlysecure.keychain.ui; +import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; import android.view.View; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.util.ActionBarHelper; -import org.sufficientlysecure.keychain.util.ExportHelper; +import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.util.Log; -public class ViewKeyAdvancedActivity extends ActionBarActivity { +public class CertifyFingerprintActivity extends BaseActivity { - ExportHelper mExportHelper; - ProviderHelper mProviderHelper; + protected Uri mDataUri; @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mExportHelper = new ExportHelper(this); - mProviderHelper = new ProviderHelper(this); - - // Inflate a "Done" custom action bar - ActionBarHelper.setOneButtonView(getSupportActionBar(), - R.string.btn_okay, R.drawable.ic_action_done, - new View.OnClickListener() { - @Override - public void onClick(View v) { - // "Done" - finish(); - } - } - ); - - setContentView(R.layout.view_key_advanced_activity); - - Uri dataUri = getIntent().getData(); - if (dataUri == null) { + mDataUri = getIntent().getData(); + if (mDataUri == null) { Log.e(Constants.TAG, "Data missing. Should be uri of key!"); finish(); return; } - Log.i(Constants.TAG, "mDataUri: " + dataUri.toString()); + setFullScreenDialogClose(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); - startFragment(savedInstanceState, dataUri); + Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + + startFragment(savedInstanceState, mDataUri); } + @Override + protected void initLayout() { + setContentView(R.layout.certify_fingerprint_activity); + } private void startFragment(Bundle savedInstanceState, Uri dataUri) { // However, if we're being restored from a previous state, @@ -79,15 +68,25 @@ public class ViewKeyAdvancedActivity extends ActionBarActivity { } // Create an instance of the fragment - ViewKeyAdvancedFragment frag = ViewKeyAdvancedFragment.newInstance(dataUri); + CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() - .replace(R.id.view_key_advanced_fragment, frag) + .replace(R.id.certify_fingerprint_fragment, frag) .commitAllowingStateLoss(); // do it immediately! getSupportFragmentManager().executePendingTransactions(); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // if a result has been returned, display a notify + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(this).show(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java new file mode 100644 index 000000000..aef705ee9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2015 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.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; + + +public class CertifyFingerprintFragment extends LoaderFragment implements + LoaderManager.LoaderCallbacks<Cursor> { + + public static final String ARG_DATA_URI = "uri"; + + private TextView mFingerprint; + + private static final int LOADER_ID_UNIFIED = 0; + + private Uri mDataUri; + + private View mActionNo; + private View mActionYes; + + /** + * Creates new instance of this fragment + */ + public static CertifyFingerprintFragment newInstance(Uri dataUri) { + CertifyFingerprintFragment frag = new CertifyFingerprintFragment(); + Bundle args = new Bundle(); + args.putParcelable(ARG_DATA_URI, dataUri); + + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.certify_fingerprint_fragment, getContainer()); + + mActionNo = view.findViewById(R.id.certify_fingerprint_button_no); + mActionYes = view.findViewById(R.id.certify_fingerprint_button_yes); + + mFingerprint = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint); + + mActionNo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + getActivity().finish(); + } + }); + mActionYes.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + certify(mDataUri); + } + }); + + return root; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + getActivity().finish(); + return; + } + + loadData(dataUri); + } + + private void loadData(Uri dataUri) { + mDataUri = dataUri; + + Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + + // Prepare the loaders. Either re-connect with an existing ones, + // or start new ones. + getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); + } + + static final String[] UNIFIED_PROJECTION = new String[]{ + KeyRings._ID, KeyRings.FINGERPRINT, + + }; + static final int INDEX_UNIFIED_FINGERPRINT = 1; + + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + setContentShown(false); + switch (id) { + case LOADER_ID_UNIFIED: { + Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); + } + + default: + return null; + } + } + + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + /* TODO better error handling? May cause problems when a key is deleted, + * because the notification triggers faster than the activity closes. + */ + // Avoid NullPointerExceptions... + if (data.getCount() == 0) { + return; + } + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + switch (loader.getId()) { + case LOADER_ID_UNIFIED: { + if (data.moveToFirst()) { + + byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT); + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); + mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint)); + + break; + } + } + + } + setContentShown(true); + } + + /** + * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. + * We need to make sure we are no longer using it. + */ + public void onLoaderReset(Loader<Cursor> loader) { + } + + private void certify(Uri dataUri) { + long keyId = 0; + try { + keyId = new ProviderHelper(getActivity()) + .getCachedPublicKeyRing(dataUri) + .extractOrGetMasterKeyId(); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "key not found!", e); + } + Intent certifyIntent = new Intent(getActivity(), CertifyKeyActivity.class); + certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{keyId}); + startActivityForResult(certifyIntent, 0); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index a97e73934..1fb88b182 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -18,24 +18,19 @@ package org.sufficientlysecure.keychain.ui; -import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; - import org.sufficientlysecure.keychain.R; /** * Signs the specified public key with the specified secret master key */ -public class CertifyKeyActivity extends ActionBarActivity { +public class CertifyKeyActivity extends BaseActivity { public static final String EXTRA_RESULT = "operation_result"; public static final String EXTRA_KEY_IDS = "extra_key_ids"; public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id"; @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - + protected void initLayout() { setContentView(R.layout.certify_key_activity); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 4d10d8639..50d5e3229 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -49,7 +49,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; @@ -82,11 +82,11 @@ public class CertifyKeyFragment extends LoaderFragment private long mSignMasterKeyId = Constants.key.none; public static final String[] USER_IDS_PROJECTION = new String[]{ - UserIds._ID, - UserIds.MASTER_KEY_ID, - UserIds.USER_ID, - UserIds.IS_PRIMARY, - UserIds.IS_REVOKED + UserPackets._ID, + UserPackets.MASTER_KEY_ID, + UserPackets.USER_ID, + UserPackets.IS_PRIMARY, + UserPackets.IS_REVOKED }; private static final int INDEX_MASTER_KEY_ID = 1; private static final int INDEX_USER_ID = 2; @@ -182,7 +182,7 @@ public class CertifyKeyFragment extends LoaderFragment @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { - Uri uri = UserIds.buildUserIdsUri(); + Uri uri = UserPackets.buildUserIdsUri(); String selection, ids[]; { @@ -196,15 +196,15 @@ public class CertifyKeyFragment extends LoaderFragment } } // put together selection string - selection = UserIds.IS_REVOKED + " = 0" + " AND " - + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + selection = UserPackets.IS_REVOKED + " = 0" + " AND " + + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " IN (" + placeholders + ")"; } return new CursorLoader(getActivity(), uri, USER_IDS_PROJECTION, selection, ids, - Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " ASC" - + ", " + Tables.USER_IDS + "." + UserIds.USER_ID + " ASC" + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC" + + ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC" ); } @@ -234,7 +234,7 @@ public class CertifyKeyFragment extends LoaderFragment long lastMasterKeyId = 0; String lastName = ""; - ArrayList<String> uids = new ArrayList<String>(); + ArrayList<String> uids = new ArrayList<>(); boolean header = true; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java index 0d8905c5b..f0ef8b9ef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java @@ -61,7 +61,7 @@ public class ConsolidateDialogActivity extends FragmentActivity { /* don't care about the results (for now?) // get returned data bundle - Bundle returnData = message.getData(); + Bundle returnData = message.getInputData(); if (returnData == null) { return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index 534ac5811..62c38d136 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -20,11 +20,10 @@ package org.sufficientlysecure.keychain.ui; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.ActionBarActivity; import org.sufficientlysecure.keychain.R; -public class CreateKeyActivity extends ActionBarActivity { +public class CreateKeyActivity extends BaseActivity { public static final String EXTRA_NAME = "name"; public static final String EXTRA_EMAIL = "email"; @@ -37,8 +36,6 @@ public class CreateKeyActivity extends ActionBarActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.create_key_activity); - // pass extras into fragment CreateKeyInputFragment frag = CreateKeyInputFragment.newInstance( @@ -48,6 +45,11 @@ public class CreateKeyActivity extends ActionBarActivity { loadFragment(null, frag, FRAG_ACTION_START); } + @Override + protected void initLayout() { + setContentView(R.layout.create_key_activity); + } + public void loadFragment(Bundle savedInstanceState, Fragment fragment, int action) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index bcc98a5d7..6e0115342 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -34,16 +34,17 @@ import android.widget.TextView; import org.spongycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; -import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; public class CreateKeyFinalFragment extends Fragment { @@ -165,7 +166,9 @@ public class CreateKeyFinalFragment extends Fragment { String userId = KeyRing.createUserId(mName, mEmail, null); mSaveKeyringParcel.mAddUserIds.add(userId); mSaveKeyringParcel.mChangePrimaryUserId = userId; - mSaveKeyringParcel.mNewPassphrase = mPassphrase; + mSaveKeyringParcel.mNewUnlock = mPassphrase != null + ? new ChangeUnlockParcel(mPassphrase, null) + : null; } } @@ -187,14 +190,14 @@ public class CreateKeyFinalFragment extends Fragment { if (returnData == null) { return; } - final SaveKeyringResult result = + final EditKeyResult result = returnData.getParcelable(OperationResult.EXTRA_RESULT); if (result == null) { Log.e(Constants.TAG, "result == null"); return; } - if (mUploadCheckbox.isChecked()) { + if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) { // result will be displayed after upload uploadKey(result); } else { @@ -224,7 +227,8 @@ public class CreateKeyFinalFragment extends Fragment { getActivity().startService(intent); } - private void uploadKey(final SaveKeyringResult saveKeyResult) { + // TODO move into EditKeyOperation + private void uploadKey(final EditKeyResult saveKeyResult) { // Send all information needed to service to upload key in other thread final Intent intent = new Intent(getActivity(), KeychainIntentService.class); @@ -232,7 +236,7 @@ public class CreateKeyFinalFragment extends Fragment { // set data uri as path to keyring Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri( - saveKeyResult.mRingMasterKeyId); + saveKeyResult.mMasterKeyId); intent.setData(blobUri); // fill values for this action diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java index 6079062a7..8aa9fa6db 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java @@ -89,7 +89,7 @@ public class CreateKeyInputFragment extends Fragment { mEmailEdit.setThreshold(1); // Start working from first character mEmailEdit.setAdapter( - new ArrayAdapter<String> + new ArrayAdapter<> (getActivity(), android.R.layout.simple_spinner_dropdown_item, ContactHelper.getPossibleUserEmails(getActivity()) ) @@ -124,7 +124,7 @@ public class CreateKeyInputFragment extends Fragment { mNameEdit.setThreshold(1); // Start working from first character mNameEdit.setAdapter( - new ArrayAdapter<String> + new ArrayAdapter<> (getActivity(), android.R.layout.simple_spinner_dropdown_item, ContactHelper.getPossibleUserNames(getActivity()) ) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java deleted file mode 100644 index 681e22e1e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui; - -import android.annotation.TargetApi; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Build.VERSION_CODES; -import android.os.Bundle; -import android.view.View; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; -import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker; - -import java.util.regex.Matcher; - -public class DecryptActivity extends DrawerActivity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.decrypt_activity); - - activateDrawerNavigation(savedInstanceState); - - View actionFile = findViewById(R.id.decrypt_files); - View actionFromClipboard = findViewById(R.id.decrypt_from_clipboard); - - actionFile.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent filesDecrypt = new Intent(DecryptActivity.this, DecryptFilesActivity.class); - filesDecrypt.setAction(DecryptFilesActivity.ACTION_DECRYPT_DATA_OPEN); - startActivity(filesDecrypt); - } - }); - - actionFromClipboard.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent clipboardDecrypt = new Intent(DecryptActivity.this, DecryptTextActivity.class); - clipboardDecrypt.setAction(DecryptTextActivity.ACTION_DECRYPT_FROM_CLIPBOARD); - startActivityForResult(clipboardDecrypt, 0); - } - }); - } - - @TargetApi(VERSION_CODES.HONEYCOMB) - @Override - protected void onResume() { - super.onResume(); - - // This is an eye candy ice cream sandwich feature, nvm on versions below - if (Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) { - - // get text from clipboard - final CharSequence clipboardText = - ClipboardReflection.getClipboardText(DecryptActivity.this); - - // if it's null, nothing to do here /o/ - if (clipboardText == null) { - return; - } - - new AsyncTask<String, Void, Boolean>() { - @Override - protected Boolean doInBackground(String... clipboardText) { - - // see if it looks like a pgp thing - Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText[0]); - boolean animate = matcher.matches(); - - // see if it looks like another pgp thing - if (!animate) { - matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText[0]); - animate = matcher.matches(); - } - return animate; - } - - @Override - protected void onPostExecute(Boolean animate) { - super.onPostExecute(animate); - - // if so, animate the clipboard icon just a bit~ - if (animate) { - SubtleAttentionSeeker.tada(findViewById(R.id.clipboard_icon), 1.5f).start(); - } - } - }.execute(clipboardText.toString()); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - // if a result has been returned, display a notify - if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { - OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); - result.createNotify(this).show(); - } else { - super.onActivityResult(requestCode, resultCode, data); - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java index 9d972d8c0..89dd4970b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java @@ -20,14 +20,13 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.api.OpenKeychainIntents; import org.sufficientlysecure.keychain.util.Log; -public class DecryptFilesActivity extends ActionBarActivity { +public class DecryptFilesActivity extends BaseActivity { /* Intents */ public static final String ACTION_DECRYPT_DATA = OpenKeychainIntents.DECRYPT_DATA; @@ -41,12 +40,15 @@ public class DecryptFilesActivity extends ActionBarActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.decrypt_files_activity); - // Handle intent actions handleActions(savedInstanceState, getIntent()); } + @Override + protected void initLayout() { + setContentView(R.layout.decrypt_files_activity); + } + /** * Handles all actions with this intent * @@ -86,7 +88,7 @@ public class DecryptFilesActivity extends ActionBarActivity { loadFragment(savedInstanceState, null, true); } else if (ACTION_DECRYPT_DATA.equals(action)) { Log.e(Constants.TAG, - "Include an Uri with setData() in your Intent!"); + "Include an Uri with setInputData() in your Intent!"); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java index 691f1c240..5606523be 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java @@ -35,13 +35,13 @@ import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; -import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.Log; import java.io.File; @@ -310,7 +310,7 @@ public class DecryptFilesFragment extends DecryptFragment { // A future open after decryption feature if () { Intent viewFile = new Intent(Intent.ACTION_VIEW); - viewFile.setData(mOutputUri); + viewFile.setInputData(mOutputUri); startActivity(viewFile); } */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 7f06d36e8..8723c7255 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -27,9 +27,9 @@ import android.widget.TextView; import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; public abstract class DecryptFragment extends Fragment { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java index 2da76088a..81a8a2ac4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java @@ -20,22 +20,21 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.api.OpenKeychainIntents; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; -import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.SingletonResult; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.Log; import java.util.regex.Matcher; -public class DecryptTextActivity extends ActionBarActivity { +public class DecryptTextActivity extends BaseActivity { /* Intents */ public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT; @@ -50,12 +49,15 @@ public class DecryptTextActivity extends ActionBarActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.decrypt_text_activity); - // Handle intent actions handleActions(savedInstanceState, getIntent()); } + @Override + protected void initLayout() { + setContentView(R.layout.decrypt_text_activity); + } + /** * Fixing broken PGP MESSAGE Strings coming from GMail/AOSP Mail */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java index fa7abf0f5..a15b23c06 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java @@ -34,13 +34,15 @@ import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ShareHelper; +import java.io.UnsupportedEncodingException; + public class DecryptTextFragment extends DecryptFragment { public static final String ARG_CIPHERTEXT = "ciphertext"; @@ -111,7 +113,7 @@ public class DecryptTextFragment extends DecryptFragment { Intent prototype = createSendIntent(text); String title = getString(R.string.title_share_file); - // we don't want to decrypt the decypted, no inception ;) + // we don't want to decrypt the decrypted, no inception ;) String[] blacklist = new String[]{ Constants.PACKAGE_NAME + ".ui.DecryptTextActivity", "org.thialfihar.android.apg.ui.DecryptActivity" @@ -194,7 +196,18 @@ public class DecryptTextFragment extends DecryptFragment { byte[] decryptedMessage = returnData .getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES); - mText.setText(new String(decryptedMessage)); + String displayMessage; + if (pgpResult.getCharset() != null) { + try { + displayMessage = new String(decryptedMessage, pgpResult.getCharset()); + } catch (UnsupportedEncodingException e) { + // if we can't decode properly, just fall back to utf-8 + displayMessage = new String(decryptedMessage); + } + } else { + displayMessage = new String(decryptedMessage); + } + mText.setText(displayMessage); pgpResult.createNotify(getActivity()).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java deleted file mode 100644 index da46de486..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.Color; -import android.os.Bundle; -import android.support.v4.app.ActionBarDrawerToggle; -import android.support.v4.view.GravityCompat; -import android.support.v4.widget.DrawerLayout; -import android.support.v4.widget.FixedDrawerLayout; -import android.support.v7.app.ActionBarActivity; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; - -public class DrawerActivity extends ActionBarActivity { - private FixedDrawerLayout mDrawerLayout; - private ListView mDrawerList; - private ActionBarDrawerToggle mDrawerToggle; - - private CharSequence mDrawerTitle; - private CharSequence mTitle; - private boolean mIsDrawerLocked = false; - - private Class mSelectedItem; - - private static final int MENU_ID_PREFERENCE = 222; - private static final int MENU_ID_HELP = 223; - - protected void deactivateDrawerNavigation() { - ((DrawerLayout) findViewById(R.id.drawer_layout)).setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); - } - - protected void activateDrawerNavigation(Bundle savedInstanceState) { - mDrawerTitle = getString(R.string.app_name); - mDrawerLayout = (FixedDrawerLayout) findViewById(R.id.drawer_layout); - mDrawerList = (ListView) findViewById(R.id.left_drawer); - ViewGroup viewGroup = (ViewGroup) findViewById(R.id.content_frame); - int leftMarginLoaded = ((ViewGroup.MarginLayoutParams) viewGroup.getLayoutParams()).leftMargin; - int leftMarginInTablets = (int) getResources().getDimension(R.dimen.drawer_size); - int errorInMarginAllowed = 5; - - // if the left margin of the loaded layout is close to the - // one used in tablets then set drawer as open and locked - if (Math.abs(leftMarginLoaded - leftMarginInTablets) < errorInMarginAllowed) { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerList); - mDrawerLayout.setScrimColor(Color.TRANSPARENT); - mIsDrawerLocked = true; - } else { - // set a custom shadow that overlays the main content when the drawer opens - mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); - mIsDrawerLocked = false; - } - - NavItem mItemIconTexts[] = new NavItem[]{ - new NavItem(R.drawable.ic_action_accounts, getString(R.string.nav_keys)), - new NavItem(R.drawable.ic_action_secure, getString(R.string.nav_encrypt_text)), - new NavItem(R.drawable.ic_action_secure, getString(R.string.nav_encrypt_files)), - new NavItem(R.drawable.ic_action_not_secure, getString(R.string.nav_decrypt)), - new NavItem(R.drawable.ic_action_view_as_list, getString(R.string.nav_apps))}; - - mDrawerList.setAdapter(new NavigationDrawerAdapter(this, R.layout.drawer_list_item, - mItemIconTexts)); - - mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); - - // enable ActionBar app icon to behave as action to toggle nav drawer - // if the drawer is not locked - if (!mIsDrawerLocked) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeButtonEnabled(true); - } - - // ActionBarDrawerToggle ties together the the proper interactions - // between the sliding drawer and the action bar app icon - mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */ - mDrawerLayout, /* DrawerLayout object */ - R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */ - R.string.drawer_open, /* "open drawer" description for accessibility */ - R.string.drawer_close /* "close drawer" description for accessibility */ - ) { - public void onDrawerClosed(View view) { - getSupportActionBar().setTitle(mTitle); - - callIntentForDrawerItem(mSelectedItem); - } - - public void onDrawerOpened(View drawerView) { - mTitle = getSupportActionBar().getTitle(); - getSupportActionBar().setTitle(mDrawerTitle); - // creates call to onPrepareOptionsMenu() - supportInvalidateOptionsMenu(); - } - }; - - if (!mIsDrawerLocked) { - mDrawerLayout.setDrawerListener(mDrawerToggle); - } else { - // If the drawer is locked open make it un-focusable - // so that it doesn't consume all the Back button presses - mDrawerLayout.setFocusableInTouchMode(false); - } - } - - /** - * Uses startActivity to call the Intent of the given class - * - * @param drawerItem the class of the drawer item you want to load. Based on Constants.DrawerItems.* - */ - public void callIntentForDrawerItem(Class drawerItem) { - // creates call to onPrepareOptionsMenu() - supportInvalidateOptionsMenu(); - - // call intent activity if selected - if (drawerItem != null) { - finish(); - overridePendingTransition(0, 0); - - Intent intent = new Intent(this, drawerItem); - startActivity(intent); - - // disable animation of activity start - overridePendingTransition(0, 0); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - if (mDrawerToggle == null) { - return super.onCreateOptionsMenu(menu); - } - - menu.add(42, MENU_ID_PREFERENCE, 100, R.string.menu_preferences); - menu.add(42, MENU_ID_HELP, 101, R.string.menu_help); - - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (mDrawerToggle == null) { - return super.onOptionsItemSelected(item); - } - - // The action bar home/up action should open or close the drawer. - // ActionBarDrawerToggle will take care of this. - if (mDrawerToggle.onOptionsItemSelected(item)) { - return true; - } - - switch (item.getItemId()) { - case MENU_ID_PREFERENCE: { - Intent intent = new Intent(this, PreferencesActivity.class); - startActivity(intent); - return true; - } - case MENU_ID_HELP: { - Intent intent = new Intent(this, HelpActivity.class); - startActivity(intent); - return true; - } - default: - return super.onOptionsItemSelected(item); - } - } - - /** - * The click listener for ListView in the navigation drawer - */ - private class DrawerItemClickListener implements ListView.OnItemClickListener { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - selectItem(position); - } - } - - private void selectItem(int position) { - // update selected item and title, then close the drawer - mDrawerList.setItemChecked(position, true); - // set selected class - mSelectedItem = Constants.DrawerItems.ARRAY[position]; - - // setTitle(mDrawerTitles[position]); - // If drawer isn't locked just close the drawer and - // it will move to the selected item by itself (via drawer toggle listener) - if (!mIsDrawerLocked) { - mDrawerLayout.closeDrawer(mDrawerList); - // else move to the selected item yourself - } else { - callIntentForDrawerItem(mSelectedItem); - } - } - - /** - * When using the ActionBarDrawerToggle, you must call it during onPostCreate() and - * onConfigurationChanged()... - */ - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - // Sync the toggle state after onRestoreInstanceState has occurred. - if (mDrawerToggle != null) { - mDrawerToggle.syncState(); - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - // Pass any configuration change to the drawer toggles - if (mDrawerToggle != null) { - mDrawerToggle.onConfigurationChanged(newConfig); - } - } - - private class NavItem { - public int icon; // res-id - public String title; - - /** - * NavItem constructor - * - * @param icon The icons resource-id - * @param title The title of the menu entry - */ - public NavItem(int icon, String title) { - super(); - this.icon = icon; - this.title = title; - } - } - - private class NavigationDrawerAdapter extends ArrayAdapter<NavItem> { - Context mContext; - int mLayoutResourceId; - NavItem mData[] = null; - - public NavigationDrawerAdapter(Context context, int layoutResourceId, NavItem[] data) { - super(context, layoutResourceId, data); - this.mLayoutResourceId = layoutResourceId; - this.mContext = context; - this.mData = data; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View row = convertView; - NavItemHolder holder; - - if (row == null) { - LayoutInflater inflater = ((Activity) mContext).getLayoutInflater(); - row = inflater.inflate(mLayoutResourceId, parent, false); - - holder = new NavItemHolder(); - holder.mImg = (ImageView) row.findViewById(R.id.drawer_item_icon); - holder.mTxtTitle = (TextView) row.findViewById(R.id.drawer_item_text); - - row.setTag(holder); - } else { - holder = (NavItemHolder) row.getTag(); - } - - NavItem item = mData[position]; - holder.mTxtTitle.setText(item.title); - holder.mImg.setImageResource(item.icon); - - return row; - } - - } - - static class NavItemHolder { - ImageView mImg; - TextView mTxtTitle; - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index 98049d89b..6dc2994cf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -19,14 +19,13 @@ package org.sufficientlysecure.keychain.ui; import android.net.Uri; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.util.Log; -public class EditKeyActivity extends ActionBarActivity { +public class EditKeyActivity extends BaseActivity { public static final String EXTRA_SAVE_KEYRING_PARCEL = "save_keyring_parcel"; @@ -36,8 +35,6 @@ public class EditKeyActivity extends ActionBarActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.edit_key_activity); - Uri dataUri = getIntent().getData(); SaveKeyringParcel saveKeyringParcel = getIntent().getParcelableExtra(EXTRA_SAVE_KEYRING_PARCEL); if (dataUri == null && saveKeyringParcel == null) { @@ -49,6 +46,11 @@ public class EditKeyActivity extends ActionBarActivity { loadFragment(savedInstanceState, dataUri, saveKeyringParcel); } + @Override + protected void initLayout() { + setContentView(R.layout.edit_key_activity); + } + private void loadFragment(Bundle savedInstanceState, Uri dataUri, SaveKeyringParcel saveKeyringParcel) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else 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 a18eb76d1..25ca6e8fd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -29,7 +29,6 @@ import android.os.Messenger; 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.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -47,12 +46,15 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; @@ -63,7 +65,6 @@ import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; -import org.sufficientlysecure.keychain.ui.util.ActionBarHelper; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; @@ -145,10 +146,8 @@ public class EditKeyFragment extends LoaderFragment implements @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - - // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setTwoButtonView(((ActionBarActivity) getActivity()).getSupportActionBar(), - R.string.btn_save, R.drawable.ic_action_save, + ((EditKeyActivity) getActivity()).setFullScreenDialogDoneClose( + R.string.btn_save, new OnClickListener() { @Override public void onClick(View v) { @@ -159,16 +158,13 @@ public class EditKeyFragment extends LoaderFragment implements saveInDatabase(mCurrentPassphrase); } } - }, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel, - new OnClickListener() { + }, new OnClickListener() { @Override public void onClick(View v) { - // cancel getActivity().setResult(Activity.RESULT_CANCELED); getActivity().finish(); } - } - ); + }); Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); SaveKeyringParcel saveKeyringParcel = getArguments().getParcelable(ARG_SAVE_KEYRING_PARCEL); @@ -189,7 +185,9 @@ public class EditKeyFragment extends LoaderFragment implements private void loadSaveKeyringParcel(SaveKeyringParcel saveKeyringParcel) { mSaveKeyringParcel = saveKeyringParcel; mPrimaryUserId = saveKeyringParcel.mChangePrimaryUserId; - mCurrentPassphrase = saveKeyringParcel.mNewPassphrase; + if (saveKeyringParcel.mNewUnlock != null) { + mCurrentPassphrase = saveKeyringParcel.mNewUnlock.mNewPassphrase; + } mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, true); mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); @@ -226,10 +224,7 @@ public class EditKeyFragment extends LoaderFragment implements mSaveKeyringParcel = new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint()); mPrimaryUserId = keyRing.getPrimaryUserIdWithFallback(); - } catch (PgpKeyNotFoundException e) { - finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND); - return; - } catch (NotFoundException e) { + } catch (PgpKeyNotFoundException | NotFoundException e) { finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND); return; } @@ -331,7 +326,7 @@ public class EditKeyFragment extends LoaderFragment implements switch (id) { case LOADER_ID_USER_IDS: { - Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri); + Uri baseUri = UserPackets.buildUserIdsUri(mDataUri); return new CursorLoader(getActivity(), baseUri, UserIdsAdapter.USER_IDS_PROJECTION, null, null, null); } @@ -379,6 +374,9 @@ public class EditKeyFragment extends LoaderFragment implements } private void changePassphrase() { +// Intent passIntent = new Intent(getActivity(), PassphraseWizardActivity.class); +// passIntent.setAction(PassphraseWizardActivity.CREATE_METHOD); +// startActivityForResult(passIntent, 12); // Message is received after passphrase is cached Handler returnHandler = new Handler() { @Override @@ -387,8 +385,10 @@ public class EditKeyFragment extends LoaderFragment implements Bundle data = message.getData(); // cache new returned passphrase! - mSaveKeyringParcel.mNewPassphrase = data - .getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE); + mSaveKeyringParcel.mNewUnlock = new ChangeUnlockParcel( + data.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE), + null + ); } } }; @@ -469,12 +469,13 @@ public class EditKeyFragment extends LoaderFragment implements } break; case EditSubkeyDialogFragment.MESSAGE_STRIP: - // toggle - if (mSaveKeyringParcel.mStripSubKeys.contains(keyId)) { - mSaveKeyringParcel.mStripSubKeys.remove(keyId); - } else { - mSaveKeyringParcel.mStripSubKeys.add(keyId); + SubkeyChange change = mSaveKeyringParcel.getSubkeyChange(keyId); + if (change == null) { + mSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null)); + break; } + // toggle + change.mDummyStrip = !change.mDummyStrip; break; } getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index b9058a37d..0d7e6056e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -8,13 +8,15 @@ import android.os.Messenger; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; +import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; +import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import java.util.Date; -public abstract class EncryptActivity extends DrawerActivity { +public abstract class EncryptActivity extends BaseActivity { public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; public static final int REQUEST_CODE_NFC = 0x00008002; @@ -82,7 +84,10 @@ public abstract class EncryptActivity extends DrawerActivity { // Send all information needed to service to edit key in other thread Intent intent = new Intent(this, KeychainIntentService.class); intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT); - intent.putExtra(KeychainIntentService.EXTRA_DATA, createEncryptBundle()); + + Bundle data = new Bundle(); + data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, createEncryptBundle()); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); // Message is received after encrypting is done in KeychainIntentService KeychainIntentServiceHandler serviceHandler = new KeychainIntentServiceHandler(this, @@ -92,28 +97,31 @@ public abstract class EncryptActivity extends DrawerActivity { super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - SignEncryptResult pgpResult = + SignEncryptResult result = message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT); - if (pgpResult.isPending()) { - if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_PASSPHRASE) == - SignEncryptResult.RESULT_PENDING_PASSPHRASE) { + PgpSignEncryptResult pgpResult = result.getPending(); + + if (pgpResult != null && pgpResult.isPending()) { + if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) == + PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) { startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded()); - } else if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_NFC) == - SignEncryptResult.RESULT_PENDING_NFC) { + } else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) == + PgpSignEncryptResult.RESULT_PENDING_NFC) { mNfcTimestamp = pgpResult.getNfcTimestamp(); - startNfcSign(pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo()); + startNfcSign(pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), + pgpResult.getNfcHash(), pgpResult.getNfcAlgo()); } else { throw new RuntimeException("Unhandled pending result!"); } return; } - if (pgpResult.success()) { - onEncryptSuccess(message, pgpResult); + if (result.success()) { + onEncryptSuccess(result); } else { - pgpResult.createNotify(EncryptActivity.this).show(); + result.createNotify(EncryptActivity.this).show(); } // no matter the result, reset parameters @@ -136,8 +144,8 @@ public abstract class EncryptActivity extends DrawerActivity { protected abstract boolean inputIsValid(); - protected abstract void onEncryptSuccess(Message message, SignEncryptResult result); + protected abstract void onEncryptSuccess(SignEncryptResult result); - protected abstract Bundle createEncryptBundle(); + protected abstract SignEncryptParcel createEncryptBundle(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java index 2d1b66daa..c5404094a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java @@ -28,7 +28,6 @@ import com.tokenautocomplete.TokenCompleteTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -164,8 +163,8 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi private void updateEncryptionKeys() { List<Object> objects = mEncryptKeyView.getObjects(); - List<Long> keyIds = new ArrayList<Long>(); - List<String> userIds = new ArrayList<String>(); + List<Long> keyIds = new ArrayList<>(); + List<String> userIds = new ArrayList<>(); for (Object object : objects) { if (object instanceof EncryptKeyCompletionView.EncryptionKey) { keyIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getKeyId()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java new file mode 100644 index 000000000..a498d0763 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker; + +import java.util.regex.Matcher; + +public class EncryptDecryptOverviewFragment extends Fragment { + + View mEncryptFile; + View mEncryptText; + View mDecryptFile; + View mDecryptFromClipboard; + View mClipboardIcon; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.encrypt_decrypt_overview_fragment, container, false); + + mEncryptFile = view.findViewById(R.id.encrypt_files); + mEncryptText = view.findViewById(R.id.encrypt_text); + mDecryptFile = view.findViewById(R.id.decrypt_files); + mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard); + mClipboardIcon = view.findViewById(R.id.clipboard_icon); + + mEncryptFile.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent encrypt = new Intent(getActivity(), EncryptFilesActivity.class); + startActivity(encrypt); + } + }); + + mEncryptText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent encrypt = new Intent(getActivity(), EncryptTextActivity.class); + startActivity(encrypt); + } + }); + + mDecryptFile.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent filesDecrypt = new Intent(getActivity(), DecryptFilesActivity.class); + filesDecrypt.setAction(DecryptFilesActivity.ACTION_DECRYPT_DATA_OPEN); + startActivity(filesDecrypt); + } + }); + + mDecryptFromClipboard.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent clipboardDecrypt = new Intent(getActivity(), DecryptTextActivity.class); + clipboardDecrypt.setAction(DecryptTextActivity.ACTION_DECRYPT_FROM_CLIPBOARD); + startActivityForResult(clipboardDecrypt, 0); + } + }); + + return view; + } + + @Override + public void onResume() { + super.onResume(); + + // get text from clipboard + final CharSequence clipboardText = + ClipboardReflection.getClipboardText(getActivity()); + + // if it's null, nothing to do here /o/ + if (clipboardText == null) { + return; + } + + new AsyncTask<String, Void, Boolean>() { + @Override + protected Boolean doInBackground(String... clipboardText) { + + // see if it looks like a pgp thing + Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText[0]); + boolean animate = matcher.matches(); + + // see if it looks like another pgp thing + if (!animate) { + matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText[0]); + animate = matcher.matches(); + } + return animate; + } + + @Override + protected void onPostExecute(Boolean animate) { + super.onPostExecute(animate); + + // if so, animate the clipboard icon just a bit~ + if (animate) { + SubtleAttentionSeeker.tada(mClipboardIcon, 1.5f).start(); + } + } + }.execute(clipboardText.toString()); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + // if a result has been returned, display a notify + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(getActivity()).show(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java index 054d85323..1286617d3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.Message; import android.support.v4.app.Fragment; import android.view.Menu; import android.view.MenuItem; @@ -29,14 +28,14 @@ import android.view.MenuItem; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.api.OpenKeychainIntents; -import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.ShareHelper; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; -import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.ShareHelper; import java.util.ArrayList; import java.util.HashSet; @@ -122,13 +121,13 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi @Override public ArrayList<Uri> getInputUris() { - if (mInputUris == null) mInputUris = new ArrayList<Uri>(); + if (mInputUris == null) mInputUris = new ArrayList<>(); return mInputUris; } @Override public ArrayList<Uri> getOutputUris() { - if (mOutputUris == null) mOutputUris = new ArrayList<Uri>(); + if (mOutputUris == null) mOutputUris = new ArrayList<>(); return mOutputUris; } @@ -170,7 +169,7 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi } @Override - public void onEncryptSuccess(Message message, SignEncryptResult pgpResult) { + public void onEncryptSuccess(SignEncryptResult result) { if (mDeleteAfterEncrypt) { for (Uri inputUri : mInputUris) { DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUri); @@ -182,29 +181,25 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi if (mShareAfterEncrypt) { // Share encrypted message/file - startActivity(sendWithChooserExcludingEncrypt(message)); + startActivity(sendWithChooserExcludingEncrypt()); } else { // Save encrypted file - pgpResult.createNotify(EncryptFilesActivity.this).show(); + result.createNotify(EncryptFilesActivity.this).show(); } } @Override - protected Bundle createEncryptBundle() { + protected SignEncryptParcel createEncryptBundle() { // fill values for this action - Bundle data = new Bundle(); + SignEncryptParcel data = new SignEncryptParcel(); - data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URIS); - data.putParcelableArrayList(KeychainIntentService.ENCRYPT_INPUT_URIS, mInputUris); + data.addInputUris(mInputUris); + data.addOutputUris(mOutputUris); - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URIS); - data.putParcelableArrayList(KeychainIntentService.ENCRYPT_OUTPUT_URIS, mOutputUris); - - data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, - Preferences.getPreferences(this).getDefaultFileCompression()); + data.setCompressionId(Preferences.getPreferences(this).getDefaultMessageCompression()); // Always use armor for messages - data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, mUseArmor); + data.setEnableAsciiArmorOutput(mUseArmor); if (isModeSymmetric()) { Log.d(Constants.TAG, "Symmetric encryption enabled!"); @@ -212,13 +207,12 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi if (passphrase.length() == 0) { passphrase = null; } - data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase); + data.setSymmetricPassphrase(passphrase); } else { - data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_MASTER_ID, mSigningKeyId); - data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, mEncryptionKeyIds); - data.putString(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_PASSPHRASE, mSigningKeyPassphrase); - data.putSerializable(KeychainIntentService.ENCRYPT_SIGNATURE_NFC_TIMESTAMP, mNfcTimestamp); - data.putByteArray(KeychainIntentService.ENCRYPT_SIGNATURE_NFC_HASH, mNfcHash); + data.setEncryptionMasterKeyIds(mEncryptionKeyIds); + data.setSignatureMasterKeyId(mSigningKeyId); + data.setSignaturePassphrase(mSigningKeyPassphrase); + data.setNfcState(mNfcHash, mNfcTimestamp); } return data; } @@ -226,8 +220,8 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi /** * Create Intent Chooser but exclude OK's EncryptActivity. */ - private Intent sendWithChooserExcludingEncrypt(Message message) { - Intent prototype = createSendIntent(message); + private Intent sendWithChooserExcludingEncrypt() { + Intent prototype = createSendIntent(); String title = getString(R.string.title_share_file); // we don't want to encrypt the encrypted, no inception ;) @@ -239,7 +233,7 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist); } - private Intent createSendIntent(Message message) { + private Intent createSendIntent() { Intent sendIntent; // file if (mOutputUris.size() == 1) { @@ -252,7 +246,7 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi sendIntent.setType("application/octet-stream"); if (!isModeSymmetric() && mEncryptionUserIds != null) { - Set<String> users = new HashSet<String>(); + Set<String> users = new HashSet<>(); for (String user : mEncryptionUserIds) { String[] userId = KeyRing.splitUserId(user); if (userId[1] != null) { @@ -309,15 +303,13 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.encrypt_files_activity); - // if called with an intent action, do not init drawer navigation if (ACTION_ENCRYPT_DATA.equals(getIntent().getAction())) { // lock drawer - deactivateDrawerNavigation(); +// deactivateDrawerNavigation(); // TODO: back button to key? } else { - activateDrawerNavigation(savedInstanceState); +// activateDrawerNavigation(savedInstanceState); } // Handle intent actions @@ -328,6 +320,11 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi } @Override + protected void initLayout() { + setContentView(R.layout.encrypt_files_activity); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.encrypt_file_activity, menu); menu.findItem(R.id.check_use_armor).setChecked(mUseArmor); @@ -379,7 +376,7 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi String action = intent.getAction(); Bundle extras = intent.getExtras(); String type = intent.getType(); - ArrayList<Uri> uris = new ArrayList<Uri>(); + ArrayList<Uri> uris = new ArrayList<>(); if (extras == null) { extras = new Bundle(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 6961f5ee7..860bd8502 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -36,10 +36,10 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FileHelper; -import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import java.io.File; import java.util.HashMap; @@ -59,7 +59,7 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt private View mShareFile; private ListView mSelectedFiles; private SelectedFilesAdapter mAdapter = new SelectedFilesAdapter(); - private final Map<Uri, Bitmap> thumbnailCache = new HashMap<Uri, Bitmap>(); + private final Map<Uri, Bitmap> thumbnailCache = new HashMap<>(); @Override public void onAttach(Activity activity) { @@ -224,7 +224,7 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt @Override public void onNotifyUpdate() { // Clear cache if needed - for (Uri uri : new HashSet<Uri>(thumbnailCache.keySet())) { + for (Uri uri : new HashSet<>(thumbnailCache.keySet())) { if (!mEncryptInterface.getInputUris().contains(uri)) { thumbnailCache.remove(uri); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java index 958daa122..2dd861d07 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.Message; import android.support.v4.app.Fragment; import android.view.Menu; import android.view.MenuItem; @@ -30,13 +29,13 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.api.OpenKeychainIntents; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; -import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.ShareHelper; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.ShareHelper; import java.util.ArrayList; import java.util.HashSet; @@ -121,13 +120,13 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv @Override public ArrayList<Uri> getInputUris() { - if (mInputUris == null) mInputUris = new ArrayList<Uri>(); + if (mInputUris == null) mInputUris = new ArrayList<>(); return mInputUris; } @Override public ArrayList<Uri> getOutputUris() { - if (mOutputUris == null) mOutputUris = new ArrayList<Uri>(); + if (mOutputUris == null) mOutputUris = new ArrayList<>(); return mOutputUris; } @@ -169,32 +168,31 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv } @Override - protected void onEncryptSuccess(Message message, SignEncryptResult pgpResult) { + protected void onEncryptSuccess(SignEncryptResult result) { if (mShareAfterEncrypt) { // Share encrypted message/file - startActivity(sendWithChooserExcludingEncrypt(message)); + startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes())); } else { // Copy to clipboard - copyToClipboard(message); - pgpResult.createNotify(EncryptTextActivity.this).show(); + copyToClipboard(result.getResultBytes()); + result.createNotify(EncryptTextActivity.this).show(); // Notify.showNotify(EncryptTextActivity.this, // R.string.encrypt_sign_clipboard_successful, Notify.Style.INFO); } } @Override - protected Bundle createEncryptBundle() { + protected SignEncryptParcel createEncryptBundle() { // fill values for this action - Bundle data = new Bundle(); + SignEncryptParcel data = new SignEncryptParcel(); - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); - data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, mMessage.getBytes()); + data.setBytes(mMessage.getBytes()); + data.setCleartextSignature(true); - data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, - Preferences.getPreferences(this).getDefaultMessageCompression()); + data.setCompressionId(Preferences.getPreferences(this).getDefaultMessageCompression()); // Always use armor for messages - data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, true); + data.setEnableAsciiArmorOutput(true); if (isModeSymmetric()) { Log.d(Constants.TAG, "Symmetric encryption enabled!"); @@ -202,26 +200,25 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv if (passphrase.length() == 0) { passphrase = null; } - data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase); + data.setSymmetricPassphrase(passphrase); } else { - data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_MASTER_ID, mSigningKeyId); - data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, mEncryptionKeyIds); - data.putString(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_PASSPHRASE, mSigningKeyPassphrase); - data.putSerializable(KeychainIntentService.ENCRYPT_SIGNATURE_NFC_TIMESTAMP, mNfcTimestamp); - data.putByteArray(KeychainIntentService.ENCRYPT_SIGNATURE_NFC_HASH, mNfcHash); + data.setEncryptionMasterKeyIds(mEncryptionKeyIds); + data.setSignatureMasterKeyId(mSigningKeyId); + data.setSignaturePassphrase(mSigningKeyPassphrase); + data.setNfcState(mNfcHash, mNfcTimestamp); } return data; } - private void copyToClipboard(Message message) { - ClipboardReflection.copyToClipboard(this, new String(message.getData().getByteArray(KeychainIntentService.RESULT_BYTES))); + private void copyToClipboard(byte[] resultBytes) { + ClipboardReflection.copyToClipboard(this, new String(resultBytes)); } /** * Create Intent Chooser but exclude OK's EncryptActivity. */ - private Intent sendWithChooserExcludingEncrypt(Message message) { - Intent prototype = createSendIntent(message); + private Intent sendWithChooserExcludingEncrypt(byte[] resultBytes) { + Intent prototype = createSendIntent(resultBytes); String title = getString(R.string.title_share_message); // we don't want to encrypt the encrypted, no inception ;) @@ -233,20 +230,21 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist); } - private Intent createSendIntent(Message message) { + private Intent createSendIntent(byte[] resultBytes) { Intent sendIntent; sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.setType("text/plain"); - sendIntent.putExtra(Intent.EXTRA_TEXT, new String(message.getData().getByteArray(KeychainIntentService.RESULT_BYTES))); + sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes)); if (!isModeSymmetric() && mEncryptionUserIds != null) { - Set<String> users = new HashSet<String>(); + Set<String> users = new HashSet<>(); for (String user : mEncryptionUserIds) { String[] userId = KeyRing.splitUserId(user); if (userId[1] != null) { users.add(userId[1]); } } + // pass trough email addresses as extra for email applications sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()])); } return sendIntent; @@ -288,15 +286,13 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.encrypt_text_activity); - // if called with an intent action, do not init drawer navigation if (ACTION_ENCRYPT_TEXT.equals(getIntent().getAction())) { // lock drawer - deactivateDrawerNavigation(); +// deactivateDrawerNavigation(); // TODO: back button to key? } else { - activateDrawerNavigation(savedInstanceState); +// activateDrawerNavigation(savedInstanceState); } // Handle intent actions @@ -305,6 +301,11 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv } @Override + protected void initLayout() { + setContentView(R.layout.encrypt_text_activity); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.encrypt_text_activity, menu); return super.onCreateOptionsMenu(menu); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java index ed29fc909..393e15cfa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java @@ -19,17 +19,16 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; import android.view.KeyEvent; import android.view.View; import android.view.Window; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; -public class FirstTimeActivity extends ActionBarActivity { +public class FirstTimeActivity extends BaseActivity { View mCreateKey; View mImportKey; @@ -39,11 +38,9 @@ public class FirstTimeActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - supportRequestWindowFeature(Window.FEATURE_NO_TITLE); - setContentView(R.layout.first_time_activity); + super.onCreate(savedInstanceState); mCreateKey = findViewById(R.id.first_time_create_key); mImportKey = findViewById(R.id.first_time_import_key); @@ -72,7 +69,11 @@ public class FirstTimeActivity extends ActionBarActivity { startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY); } }); + } + @Override + protected void initLayout() { + setContentView(R.layout.first_time_activity); } @Override @@ -91,12 +92,12 @@ public class FirstTimeActivity extends ActionBarActivity { private void finishSetup(Intent srcData) { Preferences prefs = Preferences.getPreferences(this); prefs.setFirstTime(false); - Intent intent = new Intent(this, KeyListActivity.class); + Intent intent = new Intent(this, MainActivity.class); // give intent through to display notify if (srcData != null) { intent.putExtras(srcData); } - startActivityForResult(intent, 0); + startActivity(intent); finish(); } @@ -105,4 +106,5 @@ public class FirstTimeActivity extends ActionBarActivity { public boolean onKeyDown(int keyCode, KeyEvent event) { return keyCode == KeyEvent.KEYCODE_MENU || super.onKeyDown(keyCode, event); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java index bbc1e4b1f..2eb35351e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java @@ -20,14 +20,14 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.os.Bundle; import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; +import android.view.View; + +import com.astuetz.PagerSlidingTabStrip; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; -import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; -public class HelpActivity extends ActionBarActivity { +public class HelpActivity extends BaseActivity { public static final String EXTRA_SELECTED_TAB = "selected_tab"; public static final int TAB_START = 0; @@ -44,16 +44,16 @@ public class HelpActivity extends ActionBarActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setHomeButtonEnabled(false); - - setContentView(R.layout.help_activity); + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); mViewPager = (ViewPager) findViewById(R.id.pager); - SlidingTabLayout slidingTabLayout = - (SlidingTabLayout) findViewById(R.id.sliding_tab_layout); + PagerSlidingTabStrip slidingTabLayout = + (PagerSlidingTabStrip) findViewById(R.id.sliding_tab_layout); mTabsAdapter = new PagerTabStripAdapter(this); mViewPager.setAdapter(mTabsAdapter); @@ -98,4 +98,9 @@ public class HelpActivity extends ActionBarActivity { // switch to tab selected by extra mViewPager.setCurrentItem(selectedTab); } + + @Override + protected void initLayout() { + setContentView(R.layout.help_activity); + } } 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 dfb7b3056..6638c9944 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -29,7 +29,6 @@ import android.os.Message; import android.os.Messenger; import android.os.Parcelable; import android.support.v4.app.Fragment; -import android.support.v7.app.ActionBarActivity; import android.view.View; import android.view.View.OnClickListener; @@ -51,7 +50,7 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize import java.io.IOException; import java.util.ArrayList; -public class ImportKeysActivity extends ActionBarActivity { +public class ImportKeysActivity extends BaseActivity { public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT = @@ -90,8 +89,6 @@ public class ImportKeysActivity extends ActionBarActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.import_keys_activity); - mImportButton = findViewById(R.id.import_import); mImportButton.setOnClickListener(new OnClickListener() { @Override @@ -103,6 +100,11 @@ public class ImportKeysActivity extends ActionBarActivity { handleActions(savedInstanceState, getIntent()); } + @Override + protected void initLayout() { + setContentView(R.layout.import_keys_activity); + } + protected void handleActions(Bundle savedInstanceState, Intent intent) { String action = intent.getAction(); Bundle extras = intent.getExtras(); @@ -113,105 +115,123 @@ public class ImportKeysActivity extends ActionBarActivity { extras = new Bundle(); } + if (action == null) { + startCloudFragment(savedInstanceState, null, false); + startListFragment(savedInstanceState, null, null, null); + return; + } + if (Intent.ACTION_VIEW.equals(action)) { // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) // delegate action to ACTION_IMPORT_KEY action = ACTION_IMPORT_KEY; } - if (ACTION_IMPORT_KEY.equals(action)) { - /* Keychain's own Actions */ - startFileFragment(savedInstanceState); + switch (action) { + case ACTION_IMPORT_KEY: { + /* Keychain's own Actions */ + startFileFragment(savedInstanceState); - if (dataUri != null) { - // action: directly load data - startListFragment(savedInstanceState, null, dataUri, null); - } else if (extras.containsKey(EXTRA_KEY_BYTES)) { - byte[] importData = extras.getByteArray(EXTRA_KEY_BYTES); + if (dataUri != null) { + // action: directly load data + startListFragment(savedInstanceState, null, dataUri, null); + } else if (extras.containsKey(EXTRA_KEY_BYTES)) { + byte[] importData = extras.getByteArray(EXTRA_KEY_BYTES); - // action: directly load data - startListFragment(savedInstanceState, importData, null, null); + // action: directly load data + startListFragment(savedInstanceState, importData, null, null); + } + break; } - } else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action) - || ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(action) - || ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(action)) { + case ACTION_IMPORT_KEY_FROM_KEYSERVER: + case ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE: + case ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT: { - // only used for OpenPgpService - if (extras.containsKey(EXTRA_PENDING_INTENT_DATA)) { - mPendingIntentData = extras.getParcelable(EXTRA_PENDING_INTENT_DATA); - } - if (extras.containsKey(EXTRA_QUERY) || extras.containsKey(EXTRA_KEY_ID)) { - /* simple search based on query or key id */ - - String query = null; - if (extras.containsKey(EXTRA_QUERY)) { - query = extras.getString(EXTRA_QUERY); - } else if (extras.containsKey(EXTRA_KEY_ID)) { - long keyId = extras.getLong(EXTRA_KEY_ID, 0); - if (keyId != 0) { - query = KeyFormattingUtils.convertKeyIdToHex(keyId); - } + // only used for OpenPgpService + if (extras.containsKey(EXTRA_PENDING_INTENT_DATA)) { + mPendingIntentData = extras.getParcelable(EXTRA_PENDING_INTENT_DATA); } + if (extras.containsKey(EXTRA_QUERY) || extras.containsKey(EXTRA_KEY_ID)) { + /* simple search based on query or key id */ + + String query = null; + if (extras.containsKey(EXTRA_QUERY)) { + query = extras.getString(EXTRA_QUERY); + } else if (extras.containsKey(EXTRA_KEY_ID)) { + long keyId = extras.getLong(EXTRA_KEY_ID, 0); + if (keyId != 0) { + query = KeyFormattingUtils.convertKeyIdToHex(keyId); + } + } - if (query != null && query.length() > 0) { - // display keyserver fragment with query - startCloudFragment(savedInstanceState, query, false); + if (query != null && query.length() > 0) { + // display keyserver fragment with query + startCloudFragment(savedInstanceState, query, false); - // action: search immediately - startListFragment(savedInstanceState, null, null, query); - } else { - Log.e(Constants.TAG, "Query is empty!"); - return; - } - } else if (extras.containsKey(EXTRA_FINGERPRINT)) { - /* - * search based on fingerprint, here we can enforce a check in the end - * if the right key has been downloaded - */ + // action: search immediately + startListFragment(savedInstanceState, null, null, query); + } else { + Log.e(Constants.TAG, "Query is empty!"); + return; + } + } else if (extras.containsKey(EXTRA_FINGERPRINT)) { + /* + * search based on fingerprint, here we can enforce a check in the end + * if the right key has been downloaded + */ - String fingerprint = extras.getString(EXTRA_FINGERPRINT); - if (isFingerprintValid(fingerprint)) { - String query = "0x" + fingerprint; + String fingerprint = extras.getString(EXTRA_FINGERPRINT); + if (isFingerprintValid(fingerprint)) { + String query = "0x" + fingerprint; - // display keyserver fragment with query - startCloudFragment(savedInstanceState, query, true); + // display keyserver fragment with query + startCloudFragment(savedInstanceState, query, true); - // action: search immediately - startListFragment(savedInstanceState, null, null, query); + // action: search immediately + startListFragment(savedInstanceState, null, null, query); + } + } else { + Log.e(Constants.TAG, + "IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or " + + "'fingerprint' extra!" + ); + return; } - } else { - Log.e(Constants.TAG, - "IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or " + - "'fingerprint' extra!" - ); - return; + break; } - } else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) { - // NOTE: this only displays the appropriate fragment, no actions are taken - startFileFragment(savedInstanceState); - - // no immediate actions! - startListFragment(savedInstanceState, null, null, null); - } else if (ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(action)) { - // NOTE: this only displays the appropriate fragment, no actions are taken - startFileFragment(savedInstanceState); + case ACTION_IMPORT_KEY_FROM_FILE: { + // NOTE: this only displays the appropriate fragment, no actions are taken + startFileFragment(savedInstanceState); - // 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 - startFileFragment(savedInstanceState); - // TODO!!!!! + // no immediate actions! + startListFragment(savedInstanceState, null, null, null); + break; + } + case ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN: { + // NOTE: this only displays the appropriate fragment, no actions are taken + startFileFragment(savedInstanceState); - // no immediate actions! - startListFragment(savedInstanceState, null, null, null); - } else { - startCloudFragment(savedInstanceState, null, false); - startListFragment(savedInstanceState, null, null, null); + // no immediate actions! + startListFragment(savedInstanceState, null, null, null); + break; + } + case ACTION_IMPORT_KEY_FROM_NFC: { + // NOTE: this only displays the appropriate fragment, no actions are taken + startFileFragment(savedInstanceState); + // TODO!!!!! + + // no immediate actions! + startListFragment(savedInstanceState, null, null, null); + break; + } + default: { + startCloudFragment(savedInstanceState, null, false); + startListFragment(savedInstanceState, null, null, null); + break; + } } } - private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri, String serverQuery) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else @@ -353,7 +373,7 @@ public class ImportKeysActivity extends ActionBarActivity { // We parcel this iteratively into a file - anything we can // display here, we should be able to import. ParcelableFileCache<ParcelableKeyRing> cache = - new ParcelableFileCache<ParcelableKeyRing>(this, "key_import.pcl"); + new ParcelableFileCache<>(this, "key_import.pcl"); cache.writeCache(selectedEntries); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); @@ -385,7 +405,7 @@ public class ImportKeysActivity extends ActionBarActivity { data.putString(KeychainIntentService.IMPORT_KEY_SERVER, sls.mCloudPrefs.keyserver); // get selected key entries - ArrayList<ParcelableKeyRing> keys = new ArrayList<ParcelableKeyRing>(); + ArrayList<ParcelableKeyRing> keys = new ArrayList<>(); { // change the format into ParcelableKeyRing ArrayList<ImportKeysListEntry> entries = mListFragment.getSelectedEntries(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java index 03aba344a..91ca93c36 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java @@ -21,6 +21,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.preference.PreferenceActivity; import android.support.v4.app.Fragment; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -36,8 +37,8 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.ContactHelper; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.util.List; @@ -81,7 +82,7 @@ public class ImportKeysCloudFragment extends Fragment { namesAndEmails.addAll(ContactHelper.getContactMails(getActivity())); mQueryEditText.setThreshold(3); mQueryEditText.setAdapter( - new ArrayAdapter<String> + new ArrayAdapter<> (getActivity(), android.R.layout.simple_spinner_dropdown_item, namesAndEmails ) @@ -110,11 +111,9 @@ public class ImportKeysCloudFragment extends Fragment { mConfigButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - Intent i = new Intent(mImportActivity, PreferencesActivity.class); - // GRR, for some reason I can’t set the Action or I get an incomprehensible - // exception about “modern two-pane layouts” - // i.setAction(PreferencesActivity.ACTION_PREFS_CLOUD); - startActivity(i); + Intent intent = new Intent(mImportActivity, SettingsActivity.class); + intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, SettingsActivity.CloudSearchPrefsFragment.class.getName()); + startActivity(intent); } }); 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 4fe53fb09..6a6140892 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -113,7 +113,7 @@ public class ImportKeysListFragment extends ListFragment implements return mAdapter.getSelectedEntries(); } else { Log.e(Constants.TAG, "Adapter not initialized, returning empty list"); - return new ArrayList<ImportKeysListEntry>(); + return new ArrayList<>(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java deleted file mode 100644 index ba03400d7..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> - * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui; - -import android.app.ProgressDialog; -import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.view.Menu; -import android.view.MenuItem; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.util.ExportHelper; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Preferences; - -import java.io.IOException; - -public class KeyListActivity extends DrawerActivity { - - public static final int REQUEST_CODE_RESULT_TO_LIST = 1; - - ExportHelper mExportHelper; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTitle(R.string.nav_keys); - - // if this is the first time show first time activity - Preferences prefs = Preferences.getPreferences(this); - if (prefs.isFirstTime()) { - startActivity(new Intent(this, FirstTimeActivity.class)); - finish(); - return; - } - - mExportHelper = new ExportHelper(this); - - setContentView(R.layout.key_list_activity); - - // now setup navigation drawer in DrawerActivity... - activateDrawerNavigation(savedInstanceState); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.key_list, menu); - - if (Constants.DEBUG) { - menu.findItem(R.id.menu_key_list_debug_cons).setVisible(true); - menu.findItem(R.id.menu_key_list_debug_read).setVisible(true); - menu.findItem(R.id.menu_key_list_debug_write).setVisible(true); - menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true); - } - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_key_list_add: - Intent scanQrCode = new Intent(this, QrCodeScanActivity.class); - scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT); - startActivityForResult(scanQrCode, 0); - return true; - - case R.id.menu_key_list_search_cloud: - searchCloud(); - return true; - - case R.id.menu_key_list_create: - createKey(); - return true; - - case R.id.menu_key_list_import_existing_key: - Intent intentImportExisting = new Intent(this, ImportKeysActivity.class); - intentImportExisting.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); - startActivityForResult(intentImportExisting, 0); - return true; - - case R.id.menu_key_list_export: - mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true); - return true; - - case R.id.menu_key_list_debug_cons: - consolidate(); - return true; - - case R.id.menu_key_list_debug_read: - try { - KeychainDatabase.debugBackup(this, true); - Notify.showNotify(this, "Restored debug_backup.db", Notify.Style.INFO); - getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null); - } catch (IOException e) { - Log.e(Constants.TAG, "IO Error", e); - Notify.showNotify(this, "IO Error " + e.getMessage(), Notify.Style.ERROR); - } - return true; - - case R.id.menu_key_list_debug_write: - try { - KeychainDatabase.debugBackup(this, false); - Notify.showNotify(this, "Backup to debug_backup.db completed", Notify.Style.INFO); - } catch (IOException e) { - Log.e(Constants.TAG, "IO Error", e); - Notify.showNotify(this, "IO Error: " + e.getMessage(), Notify.Style.ERROR); - } - return true; - - case R.id.menu_key_list_debug_first_time: - Preferences prefs = Preferences.getPreferences(this); - prefs.setFirstTime(true); - Intent intent = new Intent(this, FirstTimeActivity.class); - startActivity(intent); - finish(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } - - private void searchCloud() { - Intent importIntent = new Intent(this, ImportKeysActivity.class); - importIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, (String) null); // hack to show only cloud tab - startActivity(importIntent); - } - - private void createKey() { - Intent intent = new Intent(this, CreateKeyActivity.class); - startActivityForResult(intent, 0); - } - - private void consolidate() { - // Message is received after importing is done in KeychainIntentService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( - this, - getString(R.string.progress_importing), - ProgressDialog.STYLE_HORIZONTAL) { - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - // get returned data bundle - Bundle returnData = message.getData(); - if (returnData == null) { - return; - } - final ConsolidateResult result = - returnData.getParcelable(OperationResult.EXTRA_RESULT); - if (result == null) { - return; - } - - result.createNotify(KeyListActivity.this).show(); - } - } - }; - - // Send all information needed to service to import key in other thread - Intent intent = new Intent(this, KeychainIntentService.class); - - intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE); - - // fill values for this action - Bundle data = new Bundle(); - - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog(this); - - // start service with intent - startService(intent); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - // if a result has been returned, display a notify - if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { - OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); - result.createNotify(this).show(); - } else { - super.onActivityResult(requestCode, resultCode, data); - } - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index d0be052d8..3da185dd2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -31,7 +31,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; -import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -52,41 +51,37 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; -import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import com.getbase.floatingactionbutton.FloatingActionButton; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.operations.results.DeleteResult; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.ExportHelper; -import org.sufficientlysecure.keychain.util.KeyUpdateHelper; import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; -import org.sufficientlysecure.keychain.ui.widget.ListAwareSwipeRefreshLayout; import org.sufficientlysecure.keychain.ui.util.Highlighter; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.util.ParcelableFileCache; +import org.sufficientlysecure.keychain.ui.widget.ListAwareSwipeRefreshLayout; +import org.sufficientlysecure.keychain.util.ExportHelper; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; -import java.util.ArrayList; import java.util.Date; import java.util.HashMap; -import edu.cmu.cylab.starslinger.exchange.ExchangeActivity; -import edu.cmu.cylab.starslinger.exchange.ExchangeConfig; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; @@ -98,6 +93,8 @@ public class KeyListFragment extends LoaderFragment implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor> { + ExportHelper mExportHelper; + private KeyListAdapter mAdapter; private StickyListHeadersListView mStickyList; private ListAwareSwipeRefreshLayout mSwipeRefreshLayout; @@ -110,7 +107,16 @@ public class KeyListFragment extends LoaderFragment private String mQuery; private SearchView mSearchView; - boolean hideMenu = false; + private FloatingActionButton mFabQrCode; + private FloatingActionButton mFabCloud; + private FloatingActionButton mFabFile; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mExportHelper = new ExportHelper(getActivity()); + } /** * Load custom layout with StickyListView from library @@ -123,6 +129,29 @@ public class KeyListFragment extends LoaderFragment mStickyList = (StickyListHeadersListView) view.findViewById(R.id.key_list_list); mStickyList.setOnItemClickListener(this); + mFabQrCode = (FloatingActionButton) view.findViewById(R.id.fab_add_qr_code); + mFabCloud = (FloatingActionButton) view.findViewById(R.id.fab_add_cloud); + mFabFile = (FloatingActionButton) view.findViewById(R.id.fab_add_file); + + mFabQrCode.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + scanQrCode(); + } + }); + mFabCloud.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + searchCloud(); + } + }); + mFabFile.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + importFile(); + } + }); + mSwipeRefreshLayout = (ListAwareSwipeRefreshLayout) view.findViewById(R.id.key_list_swipe_container); mSwipeRefreshLayout.setOnRefreshListener(new NoScrollableSwipeRefreshLayout.OnRefreshListener() { @Override @@ -172,8 +201,8 @@ public class KeyListFragment extends LoaderFragment TextView title = (TextView) getActivity().findViewById(R.id.custom_actionbar_text); title.setText(R.string.swipe_to_update); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - hideMenu = true; - activity.invalidateOptionsMenu(); +// hideMenu = true; +// activity.invalidateOptionsMenu(); } } else { bar.setTitle(getActivity().getTitle()); @@ -184,8 +213,8 @@ public class KeyListFragment extends LoaderFragment bar.setDisplayShowCustomEnabled(false); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - hideMenu = false; - activity.invalidateOptionsMenu(); +// hideMenu = false; +// activity.invalidateOptionsMenu(); } } } @@ -198,90 +227,91 @@ public class KeyListFragment extends LoaderFragment public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + // show app name instead of "keys" from nav drawer + getActivity().setTitle(R.string.app_name); + mStickyList.setOnItemClickListener(this); mStickyList.setAreHeadersSticky(true); mStickyList.setDrawingListUnderStickyHeader(false); mStickyList.setFastScrollEnabled(true); /* - * Multi-selection is only available for Android >= 3.0 + * Multi-selection */ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mStickyList.setFastScrollAlwaysVisible(true); + mStickyList.setFastScrollAlwaysVisible(true); - mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); - mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() { + mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() { - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - android.view.MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.key_list_multi, menu); - mActionMode = mode; - return true; - } + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + android.view.MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.key_list_multi, menu); + mActionMode = mode; + return true; + } - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // get IDs for checked positions as long array - long[] ids; + // get IDs for checked positions as long array + long[] ids; - switch (item.getItemId()) { - case R.id.menu_key_list_multi_encrypt: { - ids = mAdapter.getCurrentSelectedMasterKeyIds(); - encrypt(mode, ids); - break; - } - case R.id.menu_key_list_multi_delete: { - ids = mAdapter.getCurrentSelectedMasterKeyIds(); - showDeleteKeyDialog(mode, ids, mAdapter.isAnySecretSelected()); - break; - } - case R.id.menu_key_list_multi_export: { - ids = mAdapter.getCurrentSelectedMasterKeyIds(); - ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity()); - mExportHelper.showExportKeysDialog(ids, Constants.Path.APP_DIR_FILE, - mAdapter.isAnySecretSelected()); - break; - } - case R.id.menu_key_list_multi_select_all: { - // select all - for (int i = 0; i < mStickyList.getCount(); i++) { - mStickyList.setItemChecked(i, true); - } - break; + switch (item.getItemId()) { + case R.id.menu_key_list_multi_encrypt: { + ids = mAdapter.getCurrentSelectedMasterKeyIds(); + encrypt(mode, ids); + break; + } + case R.id.menu_key_list_multi_delete: { + ids = mAdapter.getCurrentSelectedMasterKeyIds(); + showDeleteKeyDialog(mode, ids, mAdapter.isAnySecretSelected()); + break; + } + case R.id.menu_key_list_multi_export: { + ids = mAdapter.getCurrentSelectedMasterKeyIds(); + ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity()); + mExportHelper.showExportKeysDialog(ids, Constants.Path.APP_DIR_FILE, + mAdapter.isAnySecretSelected()); + break; + } + case R.id.menu_key_list_multi_select_all: { + // select all + for (int i = 0; i < mStickyList.getCount(); i++) { + mStickyList.setItemChecked(i, true); } + break; } - return true; } + return true; + } - @Override - public void onDestroyActionMode(ActionMode mode) { - mActionMode = null; - mAdapter.clearSelection(); - } + @Override + public void onDestroyActionMode(ActionMode mode) { + mActionMode = null; + mAdapter.clearSelection(); + } - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { - if (checked) { - mAdapter.setNewSelection(position, checked); - } else { - mAdapter.removeSelection(position); - } - int count = mStickyList.getCheckedItemCount(); - String keysSelected = getResources().getQuantityString( - R.plurals.key_list_selected_keys, count, count); - mode.setTitle(keysSelected); + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + if (checked) { + mAdapter.setNewSelection(position, checked); + } else { + mAdapter.removeSelection(position); } + int count = mStickyList.getCheckedItemCount(); + String keysSelected = getResources().getQuantityString( + R.plurals.key_list_selected_keys, count, count); + mode.setTitle(keysSelected); + } - }); - } + }); // We have a menu item to show in action bar. setHasOptionsMenu(true); @@ -368,9 +398,7 @@ public class KeyListFragment extends LoaderFragment // end action mode, if any if (mActionMode != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mActionMode.finish(); - } + mActionMode.finish(); } // The list should now be shown. @@ -400,7 +428,6 @@ public class KeyListFragment extends LoaderFragment startActivity(viewIntent); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) protected void encrypt(ActionMode mode, long[] masterKeyIds) { Intent intent = new Intent(getActivity(), EncryptFilesActivity.class); intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); @@ -417,7 +444,6 @@ public class KeyListFragment extends LoaderFragment * @param masterKeyIds * @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) { // Can only work on singular secret keys if (hasSecret && masterKeyIds.length > 1) { @@ -455,6 +481,15 @@ public class KeyListFragment extends LoaderFragment @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { + inflater.inflate(R.menu.key_list, menu); + + if (Constants.DEBUG) { + menu.findItem(R.id.menu_key_list_debug_cons).setVisible(true); + menu.findItem(R.id.menu_key_list_debug_read).setVisible(true); + menu.findItem(R.id.menu_key_list_debug_write).setVisible(true); + menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true); + } + // Get the searchview MenuItem searchItem = menu.findItem(R.id.menu_key_list_search); @@ -463,17 +498,10 @@ public class KeyListFragment extends LoaderFragment // Execute this when searching mSearchView.setOnQueryTextListener(this); - View searchPlate = mSearchView.findViewById(android.support.v7.appcompat.R.id.search_plate); - searchPlate.setBackgroundResource(R.drawable.keychaintheme_searchview_holo_light); - // Erase search result without focus MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - hideMenu = true; - getActivity().invalidateOptionsMenu(); - } // disable swipe-to-refresh // mSwipeRefreshLayout.setIsLocked(true); @@ -485,26 +513,66 @@ public class KeyListFragment extends LoaderFragment mQuery = null; getLoaderManager().restartLoader(0, null, KeyListFragment.this); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - hideMenu = false; - getActivity().invalidateOptionsMenu(); - } // enable swipe-to-refresh // mSwipeRefreshLayout.setIsLocked(false); return true; } }); - if (hideMenu) { - for (int i = 0; i < menu.size(); i++) { - menu.getItem(i).setVisible(false); - } - } - super.onCreateOptionsMenu(menu, inflater); } @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case R.id.menu_key_list_create: + createKey(); + return true; + + case R.id.menu_key_list_export: + mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true); + return true; + + case R.id.menu_key_list_debug_cons: + consolidate(); + return true; + + case R.id.menu_key_list_debug_read: + try { + KeychainDatabase.debugBackup(getActivity(), true); + Notify.showNotify(getActivity(), "Restored debug_backup.db", Notify.Style.INFO); + getActivity().getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null); + } catch (IOException e) { + Log.e(Constants.TAG, "IO Error", e); + Notify.showNotify(getActivity(), "IO Error " + e.getMessage(), Notify.Style.ERROR); + } + return true; + + case R.id.menu_key_list_debug_write: + try { + KeychainDatabase.debugBackup(getActivity(), false); + Notify.showNotify(getActivity(), "Backup to debug_backup.db completed", Notify.Style.INFO); + } catch (IOException e) { + Log.e(Constants.TAG, "IO Error", e); + Notify.showNotify(getActivity(), "IO Error: " + e.getMessage(), Notify.Style.ERROR); + } + return true; + + case R.id.menu_key_list_debug_first_time: + Preferences prefs = Preferences.getPreferences(getActivity()); + prefs.setFirstTime(true); + Intent intent = new Intent(getActivity(), FirstTimeActivity.class); + startActivity(intent); + getActivity().finish(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + @Override public boolean onQueryTextSubmit(String s) { return true; } @@ -526,6 +594,89 @@ public class KeyListFragment extends LoaderFragment return true; } + + private void searchCloud() { + Intent importIntent = new Intent(getActivity(), ImportKeysActivity.class); + importIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, (String) null); // hack to show only cloud tab + startActivity(importIntent); + } + + private void scanQrCode() { + Intent scanQrCode = new Intent(getActivity(), QrCodeScanActivity.class); + scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT); + startActivityForResult(scanQrCode, 0); + } + + private void importFile() { + Intent intentImportExisting = new Intent(getActivity(), ImportKeysActivity.class); + intentImportExisting.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); + startActivityForResult(intentImportExisting, 0); + } + + private void createKey() { + Intent intent = new Intent(getActivity(), CreateKeyActivity.class); + startActivityForResult(intent, 0); + } + + private void consolidate() { + // Message is received after importing is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( + getActivity(), + getString(R.string.progress_importing), + ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + // get returned data bundle + Bundle returnData = message.getData(); + if (returnData == null) { + return; + } + final ConsolidateResult result = + returnData.getParcelable(OperationResult.EXTRA_RESULT); + if (result == null) { + return; + } + + result.createNotify(getActivity()).show(); + } + } + }; + + // Send all information needed to service to import key in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE); + + // fill values for this action + Bundle data = new Bundle(); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + // if a result has been returned, display a notify + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(getActivity()).show(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + /** * Implements StickyListHeadersAdapter from library */ @@ -533,7 +684,7 @@ public class KeyListFragment extends LoaderFragment private String mQuery; private LayoutInflater mInflater; - private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>(); + private HashMap<Integer, Boolean> mSelection = new HashMap<>(); public KeyListAdapter(Context context, Cursor c, int flags) { super(context, c, flags); @@ -624,13 +775,13 @@ public class KeyListFragment extends LoaderFragment // Note: order is important! if (isRevoked) { - KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_REVOKED, true); + KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); h.mStatus.setVisibility(View.VISIBLE); h.mSlinger.setVisibility(View.GONE); h.mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); h.mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray)); } else if (isExpired) { - KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_EXPIRED, true); + KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray); h.mStatus.setVisibility(View.VISIBLE); h.mSlinger.setVisibility(View.GONE); h.mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java index 4a2b88518..0de7bb391 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java @@ -19,21 +19,18 @@ package org.sufficientlysecure.keychain.ui; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; import android.view.View; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.util.ActionBarHelper; -public class LogDisplayActivity extends ActionBarActivity { +public class LogDisplayActivity extends BaseActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Inflate a "Done" custom action bar - ActionBarHelper.setOneButtonView(getSupportActionBar(), - R.string.btn_okay, R.drawable.ic_action_done, + setFullScreenDialogClose( new View.OnClickListener() { @Override public void onClick(View v) { @@ -42,7 +39,10 @@ public class LogDisplayActivity extends ActionBarActivity { } } ); + } + @Override + protected void initLayout() { setContentView(R.layout.log_display_activity); } 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 2baebc83d..b655a7e55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -153,11 +153,11 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe switch (subEntry.mType.mLevel) { case DEBUG: ih.mSecondImg.setBackgroundColor(Color.GRAY); break; case INFO: ih.mSecondImg.setBackgroundColor(Color.BLACK); break; - case WARN: ih.mSecondImg.setBackgroundColor(Color.YELLOW); break; - case ERROR: ih.mSecondImg.setBackgroundColor(Color.RED); break; - case START: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.emphasis)); break; - case OK: ih.mSecondImg.setBackgroundColor(Color.GREEN); break; - case CANCELLED: ih.mSecondImg.setBackgroundColor(Color.RED); break; + case WARN: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break; + case ERROR: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; + case START: ih.mSecondImg.setBackgroundColor(Color.BLACK); break; + case OK: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break; + case CANCELLED: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; } } else { ih.mSecond.setVisibility(View.GONE); @@ -184,11 +184,11 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe switch (entry.mType.mLevel) { case DEBUG: ih.mImg.setBackgroundColor(Color.GRAY); break; case INFO: ih.mImg.setBackgroundColor(Color.BLACK); break; - case WARN: ih.mImg.setBackgroundColor(Color.YELLOW); break; - case ERROR: ih.mImg.setBackgroundColor(Color.RED); break; - case START: ih.mImg.setBackgroundColor(getResources().getColor(R.color.emphasis)); break; - case OK: ih.mImg.setBackgroundColor(Color.GREEN); break; - case CANCELLED: ih.mImg.setBackgroundColor(Color.RED); break; + case WARN: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break; + case ERROR: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; + case START: ih.mImg.setBackgroundColor(Color.BLACK); break; + case OK: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break; + case CANCELLED: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; } return convertView; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java new file mode 100644 index 000000000..2f29f9360 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.content.Intent; +import android.os.Bundle; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.util.Preferences; + +public class MainActivity extends NavDrawerActivity { + + @Override + public void init(Bundle savedInstanceState) { + super.init(savedInstanceState); + + // if this is the first time show first time activity + Preferences prefs = Preferences.getPreferences(this); + if (prefs.isFirstTime()) { + startActivity(new Intent(this, FirstTimeActivity.class)); + finish(); + return; + } + + Intent data = getIntent(); + // If we got an EXTRA_RESULT in the intent, show the notification + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(this).show(); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NavDrawerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NavDrawerActivity.java new file mode 100644 index 000000000..fb23c7d3a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NavDrawerActivity.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 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 org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.remote.ui.AppsListFragment; + +import it.neokree.materialnavigationdrawer.MaterialNavigationDrawer; + +public abstract class NavDrawerActivity extends MaterialNavigationDrawer { + + @Override + public void init(Bundle savedInstanceState) { + // don't open drawer on first run + disableLearningPattern(); + +// addMultiPaneSupport(); + + // set the header image + // create and set the header + setDrawerHeaderImage(R.drawable.drawer_header); + + // create sections + addSection(newSection(getString(R.string.nav_keys), R.drawable.ic_vpn_key_black_24dp, new KeyListFragment())); + addSection(newSection(getString(R.string.nav_encrypt_decrypt), R.drawable.ic_lock_black_24dp, new EncryptDecryptOverviewFragment())); + addSection(newSection(getString(R.string.title_api_registered_apps), R.drawable.ic_apps_black_24dp, new AppsListFragment())); + + // create bottom section + addBottomSection(newSection(getString(R.string.menu_preferences), R.drawable.ic_settings_black_24dp, new Intent(this, SettingsActivity.class))); + addBottomSection(newSection(getString(R.string.menu_help), R.drawable.ic_help_black_24dp, new Intent(this, HelpActivity.class))); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java index d0e40a9b8..7311f4879 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java @@ -15,7 +15,6 @@ import android.nfc.Tag; import android.nfc.tech.IsoDep; import android.os.Build; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; import android.view.WindowManager; import android.widget.Toast; @@ -23,13 +22,11 @@ import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.Locale; /** * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant @@ -38,7 +35,7 @@ import java.util.Locale; * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf */ @TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1) -public class NfcActivity extends ActionBarActivity { +public class NfcActivity extends BaseActivity { // actions public static final String ACTION_SIGN_HASH = "sign_hash"; @@ -82,8 +79,6 @@ public class NfcActivity extends ActionBarActivity { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - setContentView(R.layout.nfc_activity); - Intent intent = getIntent(); Bundle data = intent.getExtras(); String action = intent.getAction(); @@ -93,36 +88,46 @@ public class NfcActivity extends ActionBarActivity { mKeyId = data.getLong(EXTRA_KEY_ID); } - if (ACTION_SIGN_HASH.equals(action)) { - mAction = action; - mPin = data.getString(EXTRA_PIN); - mHashToSign = data.getByteArray(EXTRA_NFC_HASH_TO_SIGN); - mHashAlgo = data.getInt(EXTRA_NFC_HASH_ALGO); - mServiceIntent = data.getParcelable(EXTRA_DATA); - - Log.d(Constants.TAG, "NfcActivity mAction: " + mAction); - Log.d(Constants.TAG, "NfcActivity mPin: " + mPin); - Log.d(Constants.TAG, "NfcActivity mHashToSign as hex: " + getHex(mHashToSign)); - Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString()); - } else if (ACTION_DECRYPT_SESSION_KEY.equals(action)) { - mAction = action; - mPin = data.getString(EXTRA_PIN); - mEncryptedSessionKey = data.getByteArray(EXTRA_NFC_ENC_SESSION_KEY); - mServiceIntent = data.getParcelable(EXTRA_DATA); - - Log.d(Constants.TAG, "NfcActivity mAction: " + mAction); - Log.d(Constants.TAG, "NfcActivity mPin: " + mPin); - Log.d(Constants.TAG, "NfcActivity mEncryptedSessionKey as hex: " + getHex(mEncryptedSessionKey)); - Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString()); - } else if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { - Log.e(Constants.TAG, "This should not happen! NfcActivity.onCreate() is being called instead of onNewIntent()!"); - toast("This should not happen! Please create a new bug report that the NFC screen is restarted!"); - finish(); - } else { - Log.d(Constants.TAG, "Action not supported: " + action); + switch (action) { + case ACTION_SIGN_HASH: + mAction = action; + mPin = data.getString(EXTRA_PIN); + mHashToSign = data.getByteArray(EXTRA_NFC_HASH_TO_SIGN); + mHashAlgo = data.getInt(EXTRA_NFC_HASH_ALGO); + mServiceIntent = data.getParcelable(EXTRA_DATA); + + Log.d(Constants.TAG, "NfcActivity mAction: " + mAction); + Log.d(Constants.TAG, "NfcActivity mPin: " + mPin); + Log.d(Constants.TAG, "NfcActivity mHashToSign as hex: " + getHex(mHashToSign)); + Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString()); + break; + case ACTION_DECRYPT_SESSION_KEY: + mAction = action; + mPin = data.getString(EXTRA_PIN); + mEncryptedSessionKey = data.getByteArray(EXTRA_NFC_ENC_SESSION_KEY); + mServiceIntent = data.getParcelable(EXTRA_DATA); + + Log.d(Constants.TAG, "NfcActivity mAction: " + mAction); + Log.d(Constants.TAG, "NfcActivity mPin: " + mPin); + Log.d(Constants.TAG, "NfcActivity mEncryptedSessionKey as hex: " + getHex(mEncryptedSessionKey)); + Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString()); + break; + case NfcAdapter.ACTION_TAG_DISCOVERED: + Log.e(Constants.TAG, "This should not happen! NfcActivity.onCreate() is being called instead of onNewIntent()!"); + toast("This should not happen! Please create a new bug report that the NFC screen is restarted!"); + finish(); + break; + default: + Log.d(Constants.TAG, "Action not supported: " + action); + break; } } + @Override + protected void initLayout() { + setContentView(R.layout.nfc_activity); + } + /** * Called when the system is about to start resuming a previous activity, * disables NFC Foreground Dispatch diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java index cb15dbec2..0ccb206d1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java @@ -15,15 +15,14 @@ import android.nfc.Tag; import android.nfc.tech.IsoDep; import android.os.Build; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; import android.view.WindowManager; import android.widget.Toast; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; @@ -36,7 +35,7 @@ import java.nio.ByteBuffer; * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf */ @TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1) -public class NfcIntentActivity extends ActionBarActivity { +public class NfcIntentActivity extends BaseActivity { // special extra for OpenPgpService public static final String EXTRA_DATA = "data"; @@ -54,8 +53,6 @@ public class NfcIntentActivity extends ActionBarActivity { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - setContentView(R.layout.nfc_activity); - Intent intent = getIntent(); Bundle data = intent.getExtras(); String action = intent.getAction(); @@ -87,7 +84,11 @@ public class NfcIntentActivity extends ActionBarActivity { Log.e(Constants.TAG, "IOException!", e); finish(); } + } + @Override + protected void initLayout() { + setContentView(R.layout.nfc_activity); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index deff648ba..d5ca08936 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -68,6 +68,8 @@ public class PassphraseDialogActivity extends FragmentActivity { // special extra for OpenPgpService public static final String EXTRA_DATA = "data"; + private static final int REQUEST_CODE_ENTER_PATTERN = 2; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -90,6 +92,40 @@ public class PassphraseDialogActivity extends FragmentActivity { show(this, keyId, serviceIntent); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_ENTER_PATTERN: { + /* + * NOTE that there are 4 possible result codes!!! + */ + switch (resultCode) { + case RESULT_OK: + // The user passed + break; + case RESULT_CANCELED: + // The user cancelled the task + break; +// case LockPatternActivity.RESULT_FAILED: +// // The user failed to enter the pattern +// break; +// case LockPatternActivity.RESULT_FORGOT_PATTERN: +// // The user forgot the pattern and invoked your recovery Activity. +// break; + } + + /* + * In any case, there's always a key EXTRA_RETRY_COUNT, which holds + * the number of tries that the user did. + */ +// int retryCount = data.getIntExtra( +// LockPatternActivity.EXTRA_RETRY_COUNT, 0); + + break; + } + } + } + /** * Shows passphrase dialog to cache a new passphrase the user enters for using it later for * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks @@ -138,7 +174,7 @@ public class PassphraseDialogActivity extends FragmentActivity { CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme); - alert.setTitle(R.string.title_authentication); + alert.setTitle(R.string.title_unlock); String userId; CanonicalizedSecretKey.SecretKeyType keyType = CanonicalizedSecretKey.SecretKeyType.PASSPHRASE; @@ -170,8 +206,11 @@ public class PassphraseDialogActivity extends FragmentActivity { case PASSPHRASE: message = getString(R.string.passphrase_for, userId); break; + case PIN: + message = getString(R.string.pin_for, userId); + break; case DIVERT_TO_CARD: - message = getString(R.string.yubikey_pin, userId); + message = getString(R.string.yubikey_pin_for, userId); break; default: message = "This should not happen!"; @@ -209,40 +248,52 @@ public class PassphraseDialogActivity extends FragmentActivity { } }); - // Hack to open keyboard. - // This is the only method that I found to work across all Android versions - // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ - // Notes: * onCreateView can't be used because we want to add buttons to the dialog - // * opening in onActivityCreated does not work on Android 4.4 - mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - mPassphraseEditText.post(new Runnable() { - @Override - public void run() { - if (getActivity() == null || mPassphraseEditText == null) { - return; + + if (keyType == CanonicalizedSecretKey.SecretKeyType.PATTERN) { + // start pattern dialog and show progress circle here... +// Intent patternActivity = new Intent(getActivity(), LockPatternActivity.class); +// patternActivity.putExtra(LockPatternActivity.EXTRA_PATTERN, "123"); +// startActivityForResult(patternActivity, REQUEST_CODE_ENTER_PATTERN); + mInput.setVisibility(View.GONE); + mProgress.setVisibility(View.VISIBLE); + } else { + // Hack to open keyboard. + // This is the only method that I found to work across all Android versions + // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ + // Notes: * onCreateView can't be used because we want to add buttons to the dialog + // * opening in onActivityCreated does not work on Android 4.4 + mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + mPassphraseEditText.post(new Runnable() { + @Override + public void run() { + if (getActivity() == null || mPassphraseEditText == null) { + return; + } + InputMethodManager imm = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT); } - InputMethodManager imm = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT); - } - }); + }); + } + }); + mPassphraseEditText.requestFocus(); + + mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); + mPassphraseEditText.setOnEditorActionListener(this); + + if (keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubikeyPin()) { + mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } else if (keyType == CanonicalizedSecretKey.SecretKeyType.PIN) { + mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } else { + mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); } - }); - mPassphraseEditText.requestFocus(); - - mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); - mPassphraseEditText.setOnEditorActionListener(this); - if (keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubikeyPin()) { - mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD); - } else { - mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); } - mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); - AlertDialog dialog = alert.create(); dialog.setButton(DialogInterface.BUTTON_POSITIVE, activity.getString(android.R.string.ok), (DialogInterface.OnClickListener) null); @@ -264,7 +315,7 @@ public class PassphraseDialogActivity extends FragmentActivity { // Early breakout if we are dealing with a symmetric key if (mSecretRing == null) { PassphraseCacheService.addCachedPassphrase(getActivity(), - Constants.key.symmetric, Constants.key.symmetric, passphrase, + Constants.key.symmetric, Constants.key.symmetric, passphrase, getString(R.string.passp_cache_notif_pwd)); finishCaching(passphrase); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java new file mode 100644 index 000000000..2e838535d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java @@ -0,0 +1,575 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.Ndef; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentTransaction; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import org.sufficientlysecure.keychain.R; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.List; + +@TargetApi(Build.VERSION_CODES.HONEYCOMB) +public class PassphraseWizardActivity extends FragmentActivity { +//public class PassphraseWizardActivity extends FragmentActivity implements LockPatternView.OnPatternListener { + //create or authenticate + public String selectedAction; + //for lockpattern + public static char[] pattern; + private static String passphrase = ""; + //nfc string + private static byte[] output = new byte[8]; + + public static final String CREATE_METHOD = "create"; + public static final String AUTHENTICATION = "authenticate"; + + NfcAdapter adapter; + PendingIntent pendingIntent; + IntentFilter writeTagFilters[]; + boolean writeMode; + Tag myTag; + boolean writeNFC = false; + boolean readNFC = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getActionBar() != null) { + getActionBar().setTitle(R.string.unlock_method); + } + + selectedAction = getIntent().getAction(); + if (savedInstanceState == null) { + SelectMethods selectMethods = new SelectMethods(); + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.add(R.id.fragmentContainer, selectMethods).commit(); + } + setContentView(R.layout.passphrase_wizard); + + adapter = NfcAdapter.getDefaultAdapter(this); + if (adapter != null) { + pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, PassphraseWizardActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); + IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED); + tagDetected.addCategory(Intent.CATEGORY_DEFAULT); + writeTagFilters = new IntentFilter[]{tagDetected}; + } + } + + public void noPassphrase(View view) { + passphrase = ""; + Toast.makeText(this, R.string.no_passphrase_set, Toast.LENGTH_SHORT).show(); + this.finish(); + } + + public void passphrase(View view) { + Passphrase passphrase = new Passphrase(); + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fragmentContainer, passphrase).addToBackStack(null).commit(); + } + + public void startLockpattern(View view) { + if (getActionBar() != null) { + getActionBar().setTitle(R.string.draw_lockpattern); + } +// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction); +// LockPatternFragment lpf = LockPatternFragment.newInstance("asd"); + +// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); +// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit(); + } + + public void cancel(View view) { + this.finish(); + } + + public void savePassphrase(View view) { + EditText passphrase = (EditText) findViewById(R.id.passphrase); + passphrase.setError(null); + String pw = passphrase.getText().toString(); + //check and save passphrase + if (selectedAction.equals(CREATE_METHOD)) { + EditText passphraseAgain = (EditText) findViewById(R.id.passphraseAgain); + passphraseAgain.setError(null); + String pwAgain = passphraseAgain.getText().toString(); + + if (!TextUtils.isEmpty(pw)) { + if (!TextUtils.isEmpty(pwAgain)) { + if (pw.equals(pwAgain)) { + PassphraseWizardActivity.passphrase = pw; + Toast.makeText(this, getString(R.string.passphrase_saved), Toast.LENGTH_SHORT).show(); + this.finish(); + } else { + passphrase.setError(getString(R.string.passphrase_invalid)); + passphrase.requestFocus(); + } + } else { + passphraseAgain.setError(getString(R.string.missing_passphrase)); + passphraseAgain.requestFocus(); + } + } else { + passphrase.setError(getString(R.string.missing_passphrase)); + passphrase.requestFocus(); + } + } + //check for right passphrase + if (selectedAction.equals(AUTHENTICATION)) { + if (pw.equals(PassphraseWizardActivity.passphrase)) { + Toast.makeText(this, getString(R.string.unlocked), Toast.LENGTH_SHORT).show(); + this.finish(); + } else { + passphrase.setError(getString(R.string.passphrase_invalid)); + passphrase.requestFocus(); + } + } + } + + public void NFC(View view) { + if (adapter != null) { + if (getActionBar() != null) { + getActionBar().setTitle(R.string.nfc_title); + } + NFCFragment nfc = new NFCFragment(); + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fragmentContainer, nfc).addToBackStack(null).commit(); + + //if you want to create a new method or just authenticate + if (CREATE_METHOD.equals(selectedAction)) { + writeNFC = true; + } else if (AUTHENTICATION.equals(selectedAction)) { + readNFC = true; + } + + if (!adapter.isEnabled()) { + showAlertDialog(getString(R.string.enable_nfc), true); + } + } else { + showAlertDialog(getString(R.string.no_nfc_support), false); + } + } + + @Override + protected void onNewIntent(Intent intent) { + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { + myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + + if (writeNFC && CREATE_METHOD.equals(selectedAction)) { + //write new password on NFC tag + try { + if (myTag != null) { + write(myTag); + writeNFC = false; //just write once + Toast.makeText(this, R.string.nfc_write_succesful, Toast.LENGTH_SHORT).show(); + //advance to lockpattern +// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction); +// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); +// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit(); + } + } catch (IOException | FormatException e) { + e.printStackTrace(); + } + + } else if (readNFC && AUTHENTICATION.equals(selectedAction)) { + //read pw from NFC tag + try { + if (myTag != null) { + //if tag detected, read tag + String pwtag = read(myTag); + if (output != null && pwtag.equals(output.toString())) { + + //passwort matches, go to next view + Toast.makeText(this, R.string.passphrases_match + "!", Toast.LENGTH_SHORT).show(); + +// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction); +// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); +// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit(); + readNFC = false; //just once + } else { + //passwort doesnt match + TextView nfc = (TextView) findViewById(R.id.nfcText); + nfc.setText(R.string.nfc_wrong_tag); + } + } + } catch (IOException | FormatException e) { + e.printStackTrace(); + } + } + } + } + + private void write(Tag tag) throws IOException, FormatException { + //generate new random key and write them on the tag + SecureRandom sr = new SecureRandom(); + sr.nextBytes(output); + NdefRecord[] records = {createRecord(output.toString())}; + NdefMessage message = new NdefMessage(records); + Ndef ndef = Ndef.get(tag); + ndef.connect(); + ndef.writeNdefMessage(message); + ndef.close(); + } + + private String read(Tag tag) throws IOException, FormatException { + //read string from tag + String password = null; + Ndef ndef = Ndef.get(tag); + ndef.connect(); + NdefMessage ndefMessage = ndef.getCachedNdefMessage(); + + NdefRecord[] records = ndefMessage.getRecords(); + for (NdefRecord ndefRecord : records) { + if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) { + try { + password = readText(ndefRecord); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + } + ndef.close(); + return password; + } + + private String readText(NdefRecord record) throws UnsupportedEncodingException { + //low-level method for reading nfc + byte[] payload = record.getPayload(); + String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16"; + int languageCodeLength = payload[0] & 0063; + return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding); + } + + private NdefRecord createRecord(String text) throws UnsupportedEncodingException { + //low-level method for writing nfc + String lang = "en"; + byte[] textBytes = text.getBytes(); + byte[] langBytes = lang.getBytes("US-ASCII"); + int langLength = langBytes.length; + int textLength = textBytes.length; + byte[] payload = new byte[1 + langLength + textLength]; + + // set status byte (see NDEF spec for actual bits) + payload[0] = (byte) langLength; + // copy langbytes and textbytes into payload + System.arraycopy(langBytes, 0, payload, 1, langLength); + System.arraycopy(textBytes, 0, payload, 1 + langLength, textLength); + return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payload); + } + + public void showAlertDialog(String message, boolean nfc) { + //This method shows an AlertDialog + AlertDialog.Builder alert = new AlertDialog.Builder(this); + alert.setTitle("Information").setMessage(message).setPositiveButton("Ok", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + } + } + ); + if (nfc) { + + alert.setNeutralButton(R.string.nfc_settings, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialogInterface, int i) { + startActivity(new Intent(Settings.ACTION_NFC_SETTINGS)); + } + } + ); + } + alert.show(); + } + + @Override + public void onPause() { + //pause this app and free nfc intent + super.onPause(); + if (adapter != null) { + WriteModeOff(); + } + } + + @Override + public void onResume() { + //resume this app and get nfc intent + super.onResume(); + if (adapter != null) { + WriteModeOn(); + } + } + + private void WriteModeOn() { + //enable nfc for this view + writeMode = true; + adapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null); + } + + private void WriteModeOff() { + //disable nfc for this view + writeMode = false; + adapter.disableForegroundDispatch(this); + } + + public static class SelectMethods extends Fragment { +// private OnFragmentInteractionListener mListener; + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + */ + public SelectMethods() { + + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public void onResume() { + super.onResume(); + if (getActivity().getActionBar() != null) { + getActivity().getActionBar().setTitle(R.string.unlock_method); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.passphrase_wizard_fragment_select_methods, container, false); + } + +// @Override +// public void onAttach(Activity activity) { +// super.onAttach(activity); +// try { +// mListener = (OnFragmentInteractionListener) activity; +// } catch (ClassCastException e) { +// throw new ClassCastException(activity.toString() +// + " must implement OnFragmentInteractionListener"); +// } +// } +// +// @Override +// public void onDetach() { +// super.onDetach(); +// mListener = null; +// } + + /** + * This interface must be implemented by activities that contain this + * fragment to allow an interaction in this fragment to be communicated + * to the activity and potentially other fragments contained in that + * activity. + * <p/> + * See the Android Training lesson <a href= + * "http://developer.android.com/training/basics/fragments/communicating.html" + * >Communicating with Other Fragments</a> for more information. + */ +// public static interface OnFragmentInteractionListener { +// public void onFragmentInteraction(Uri uri); +// } + + } + + + // /** +// * A simple {@link android.support.v4.app.Fragment} subclass. +// * Activities that contain this fragment must implement the +// * {@link com.haibison.android.lockpattern.Passphrase.OnFragmentInteractionListener} interface +// * to handle interaction events. +// */ + public static class Passphrase extends Fragment { + +// private OnFragmentInteractionListener mListener; + + public Passphrase() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View view = inflater.inflate(R.layout.passphrase_wizard_fragment_passphrase, container, false); + EditText passphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain); + TextView passphraseText = (TextView) view.findViewById(R.id.passphraseText); + TextView passphraseTextAgain = (TextView) view.findViewById(R.id.passphraseTextAgain); + String selectedAction = getActivity().getIntent().getAction(); + if (selectedAction.equals(AUTHENTICATION)) { + passphraseAgain.setVisibility(View.GONE); + passphraseTextAgain.setVisibility(View.GONE); + passphraseText.setText(R.string.enter_passphrase); +// getActivity().getActionBar().setTitle(R.string.enter_passphrase); + } else if (selectedAction.equals(CREATE_METHOD)) { + passphraseAgain.setVisibility(View.VISIBLE); + passphraseTextAgain.setVisibility(View.VISIBLE); + passphraseText.setText(R.string.passphrase); +// getActivity().getActionBar().setTitle(R.string.set_passphrase); + } + return view; + } + +// @Override +// public void onAttach(Activity activity) { +// super.onAttach(activity); +// try { +// mListener = (OnFragmentInteractionListener) activity; +// } catch (ClassCastException e) { +// throw new ClassCastException(activity.toString() +// + " must implement OnFragmentInteractionListener"); +// } +// } +// +// @Override +// public void onDetach() { +// super.onDetach(); +// mListener = null; +// } + +// /** +// * This interface must be implemented by activities that contain this +// * fragment to allow an interaction in this fragment to be communicated +// * to the activity and potentially other fragments contained in that +// * activity. +// * <p/> +// * See the Android Training lesson <a href= +// * "http://developer.android.com/training/basics/fragments/communicating.html" +// * >Communicating with Other Fragments</a> for more information. +// */ +// public interface OnFragmentInteractionListener { +// public void onFragmentInteraction(Uri uri); +// } + } + + + /** + * A simple {@link android.support.v4.app.Fragment} subclass. + * Activities that contain this fragment must implement the + * interface + * to handle interaction events. + * Use the method to + * create an instance of this fragment. + */ + public static class NFCFragment extends Fragment { + // TODO: Rename parameter arguments, choose names that match + // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER + private static final String ARG_PARAM1 = "param1"; + private static final String ARG_PARAM2 = "param2"; + + // TODO: Rename and change types of parameters + private String mParam1; + private String mParam2; + +// private OnFragmentInteractionListener mListener; + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param param1 Parameter 1. + * @param param2 Parameter 2. + * @return A new instance of fragment SelectMethods. + */ + // TODO: Rename and change types and number of parameters + public static NFCFragment newInstance(String param1, String param2) { + NFCFragment fragment = new NFCFragment(); + Bundle args = new Bundle(); + args.putString(ARG_PARAM1, param1); + args.putString(ARG_PARAM2, param2); + fragment.setArguments(args); + return fragment; + } + + public NFCFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mParam1 = getArguments().getString(ARG_PARAM1); + mParam2 = getArguments().getString(ARG_PARAM2); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.passphrase_wizard_fragment_nfc, container, false); + } + +// // TODO: Rename method, update argument and hook method into UI event +// public void onButtonPressed(Uri uri) { +// if (mListener != null) { +// mListener.onFragmentInteraction(uri); +// } +// } + +// @Override +// public void onAttach(Activity activity) { +// super.onAttach(activity); +// try { +// mListener = (OnFragmentInteractionListener) activity; +// } catch (ClassCastException e) { +// throw new ClassCastException(activity.toString() +// + " must implement OnFragmentInteractionListener"); +// } +// } + + +// @Override +// public void onDetach() { +// super.onDetach(); +// mListener = null; +// } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeScanActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeScanActivity.java index 5966870df..1a7a028c6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeScanActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeScanActivity.java @@ -78,7 +78,11 @@ public class QrCodeScanActivity extends FragmentActivity { // scan using xzing's Barcode Scanner and return result parcel in OpenKeychain returnResult = true; - new IntentIntegrator(this).initiateScan(); + IntentIntegrator integrator = new IntentIntegrator(this); + integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES) + .setPrompt(getString(R.string.import_qr_code_text)) + .setResultDisplayDuration(0) + .initiateScan(); } else if (ACTION_QR_CODE_API.equals(action)) { // scan using xzing's Barcode Scanner from outside OpenKeychain @@ -168,7 +172,7 @@ public class QrCodeScanActivity extends FragmentActivity { return; } - if ( ! result.success()) { + if (!result.success()) { // only return if no success... Intent data = new Intent(); data.putExtras(returnData); @@ -199,7 +203,7 @@ public class QrCodeScanActivity extends FragmentActivity { data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver); ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); - ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<ParcelableKeyRing>(); + ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java index cf0c3eb88..d3c1d971a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java @@ -20,57 +20,56 @@ package org.sufficientlysecure.keychain.ui; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; +import android.support.v4.app.ActivityCompat; +import android.support.v7.widget.CardView; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.ImageView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.util.ActionBarHelper; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; +import org.sufficientlysecure.keychain.util.Log; -public class QrCodeViewActivity extends ActionBarActivity { +public class QrCodeViewActivity extends BaseActivity { - private ImageView mFingerprintQrCode; + private ImageView mQrCode; + private CardView mQrCodeLayout; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Inflate a "Done" custom action bar - ActionBarHelper.setOneButtonView(getSupportActionBar(), - R.string.btn_okay, R.drawable.ic_action_done, + setFullScreenDialogClose( new View.OnClickListener() { @Override public void onClick(View v) { // "Done" - finish(); + ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); } } ); - setContentView(R.layout.qr_code_activity); - Uri dataUri = getIntent().getData(); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); - finish(); + ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); return; } - mFingerprintQrCode = (ImageView) findViewById(R.id.qr_code_image); + mQrCode = (ImageView) findViewById(R.id.qr_code_image); + mQrCodeLayout = (CardView) findViewById(R.id.qr_code_image_layout); - mFingerprintQrCode.setOnClickListener(new View.OnClickListener() { + mQrCodeLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - finish(); + ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); } }); @@ -82,7 +81,7 @@ public class QrCodeViewActivity extends ActionBarActivity { if (blob == null) { Log.e(Constants.TAG, "key not found!"); Notify.showNotify(this, R.string.error_key_not_found, Style.ERROR); - finish(); + ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); } String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); @@ -91,37 +90,26 @@ public class QrCodeViewActivity extends ActionBarActivity { // create a minimal size qr code, we can keep this in ram no problem final Bitmap qrCode = QrCodeUtils.getQRCodeBitmap(qrCodeContent, 0); - mFingerprintQrCode.getViewTreeObserver().addOnGlobalLayoutListener( + mQrCode.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - // create actual bitmap in display dimensions - Bitmap scaled = Bitmap.createScaledBitmap(qrCode, - mFingerprintQrCode.getWidth(), mFingerprintQrCode.getWidth(), false); - mFingerprintQrCode.setImageBitmap(scaled); - } - }); + @Override + public void onGlobalLayout() { + // create actual bitmap in display dimensions + Bitmap scaled = Bitmap.createScaledBitmap(qrCode, + mQrCode.getWidth(), mQrCode.getWidth(), false); + mQrCode.setImageBitmap(scaled); + } + }); } catch (ProviderHelper.NotFoundException e) { Log.e(Constants.TAG, "key not found!", e); Notify.showNotify(this, R.string.error_key_not_found, Style.ERROR); - finish(); + ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); } } @Override - protected void onResume() { - super.onResume(); - - // custom activity transition to get zoom in effect - this.overridePendingTransition(R.anim.qr_code_zoom_enter, android.R.anim.fade_out); - } - - @Override - protected void onPause() { - super.onPause(); - - // custom activity transition to get zoom out effect - this.overridePendingTransition(0, R.anim.qr_code_zoom_exit); + protected void initLayout() { + setContentView(R.layout.qr_code_activity); } }
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java index 20e1bbe97..d1df2906d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java @@ -27,13 +27,9 @@ import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.support.v4.app.FragmentActivity; -import android.support.v7.app.ActionBarActivity; import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.NumberPicker; -import android.widget.Spinner; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -50,13 +46,12 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache; import java.io.IOException; import java.util.ArrayList; -import java.util.List; import edu.cmu.cylab.starslinger.exchange.ExchangeActivity; import edu.cmu.cylab.starslinger.exchange.ExchangeConfig; @TargetApi(Build.VERSION_CODES.HONEYCOMB) -public class SafeSlingerActivity extends ActionBarActivity { +public class SafeSlingerActivity extends BaseActivity { private static final int REQUEST_CODE_SAFE_SLINGER = 211; @@ -69,51 +64,17 @@ public class SafeSlingerActivity extends ActionBarActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.safe_slinger_activity); - mMasterKeyId = getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0); - // NOTE: there are two versions of this layout, for API >= 11 and one for < 11 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - NumberPicker picker = (NumberPicker) findViewById(R.id.safe_slinger_picker); - picker.setMinValue(2); - picker.setMaxValue(10); - picker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { - @Override - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - mSelectedNumber = newVal; - } - }); - } else { - Spinner spinner = (Spinner) findViewById(R.id.safe_slinger_spinner); - - List<String> list = new ArrayList<String>(); - list.add("2"); - list.add("3"); - list.add("4"); - list.add("5"); - list.add("6"); - list.add("7"); - list.add("8"); - list.add("9"); - list.add("10"); - - ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this, - android.R.layout.simple_spinner_item, list); - dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(dataAdapter); - spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - mSelectedNumber = position + 2; - } - - @Override - public void onNothingSelected(AdapterView<?> parent) { - - } - }); - } + NumberPicker picker = (NumberPicker) findViewById(R.id.safe_slinger_picker); + picker.setMinValue(2); + picker.setMaxValue(10); + picker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mSelectedNumber = newVal; + } + }); ImageView buttonIcon = (ImageView) findViewById(R.id.safe_slinger_button_image); buttonIcon.setColorFilter(getResources().getColor(R.color.tertiary_text_light), @@ -128,6 +89,11 @@ public class SafeSlingerActivity extends ActionBarActivity { }); } + @Override + protected void initLayout() { + setContentView(R.layout.safe_slinger_activity); + } + private void startExchange(long masterKeyId, int number) { // retrieve public key blob and start SafeSlinger Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(masterKeyId); @@ -197,7 +163,7 @@ public class SafeSlingerActivity extends ActionBarActivity { certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result); certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds()); certifyIntent.putExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, mMasterKeyId); - startActivityForResult(certifyIntent, KeyListActivity.REQUEST_CODE_RESULT_TO_LIST); + startActivityForResult(certifyIntent, 0); // mExchangeMasterKeyId = null; } @@ -221,7 +187,7 @@ public class SafeSlingerActivity extends ActionBarActivity { // We parcel this iteratively into a file - anything we can // display here, we should be able to import. ParcelableFileCache<ParcelableKeyRing> cache = - new ParcelableFileCache<ParcelableKeyRing>(activity, "key_import.pcl"); + new ParcelableFileCache<>(activity, "key_import.pcl"); cache.writeCache(it.size(), it.iterator()); // fill values for this action @@ -249,7 +215,7 @@ public class SafeSlingerActivity extends ActionBarActivity { } private static ArrayList<ParcelableKeyRing> getSlingedKeys(Bundle extras) { - ArrayList<ParcelableKeyRing> list = new ArrayList<ParcelableKeyRing>(); + ArrayList<ParcelableKeyRing> list = new ArrayList<>(); if (extras != null) { byte[] d; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java deleted file mode 100644 index 148aa7d67..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> - * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> - * - * 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.v7.app.ActionBarActivity; -import android.view.View; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.util.ActionBarHelper; - -public class SelectPublicKeyActivity extends ActionBarActivity { - - // Actions for internal use only: - public static final String ACTION_SELECT_PUBLIC_KEYS = Constants.INTENT_PREFIX - + "SELECT_PUBLIC_KEYRINGS"; - - public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; - - public static final String RESULT_EXTRA_MASTER_KEY_IDS = "master_key_ids"; - public static final String RESULT_EXTRA_USER_IDS = "user_ids"; - - SelectPublicKeyFragment mSelectFragment; - - long mSelectedMasterKeyIds[]; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done, - new View.OnClickListener() { - @Override - public void onClick(View v) { - // ok - okClicked(); - } - }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() { - @Override - public void onClick(View v) { - // cancel - cancelClicked(); - } - } - ); - - setContentView(R.layout.select_public_key_activity); - - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - handleIntent(getIntent()); - - // Check that the activity is using the layout version with - // the fragment_container FrameLayout - if (findViewById(R.id.select_public_key_fragment_container) != null) { - - // However, if we're being restored from a previous state, - // then we don't need to do anything and should return or else - // we could end up with overlapping fragments. - if (savedInstanceState != null) { - return; - } - - // Create an instance of the fragment - mSelectFragment = SelectPublicKeyFragment.newInstance(mSelectedMasterKeyIds); - - // Add the fragment to the 'fragment_container' FrameLayout - getSupportFragmentManager().beginTransaction() - .add(R.id.select_public_key_fragment_container, mSelectFragment).commit(); - } - - // TODO: reimplement! - // mFilterLayout = findViewById(R.id.layout_filter); - // mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); - // mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); - // - // mClearFilterButton.setOnClickListener(new OnClickListener() { - // public void onClick(View v) { - // handleIntent(new Intent()); - // } - // }); - - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - // TODO: reimplement search! - - // String searchString = null; - // if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - // searchString = intent.getStringExtra(SearchManager.QUERY); - // if (searchString != null && searchString.trim().length() == 0) { - // searchString = null; - // } - // } - - // if (searchString == null) { - // mFilterLayout.setVisibility(View.GONE); - // } else { - // mFilterLayout.setVisibility(View.VISIBLE); - // mFilterInfo.setText(getString(R.string.filterInfo, searchString)); - // } - - // preselected master keys - mSelectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); - } - - private void cancelClicked() { - setResult(RESULT_CANCELED, null); - finish(); - } - - private void okClicked() { - Intent data = new Intent(); - data.putExtra(RESULT_EXTRA_MASTER_KEY_IDS, mSelectFragment.getSelectedMasterKeyIds()); - data.putExtra(RESULT_EXTRA_USER_IDS, mSelectFragment.getSelectedUserIds()); - setResult(RESULT_OK, data); - finish(); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java index af583bf89..afec3bf06 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java @@ -42,7 +42,6 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; -import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; @@ -216,7 +215,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T public long[] getSelectedMasterKeyIds() { // mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key // ids! - Vector<Long> vector = new Vector<Long>(); + Vector<Long> vector = new Vector<>(); for (int i = 0; i < getListView().getCount(); ++i) { if (getListView().isItemChecked(i)) { vector.add(mAdapter.getMasterKeyId(i)); @@ -238,7 +237,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T * @return */ public String[] getSelectedUserIds() { - Vector<String> userIds = new Vector<String>(); + Vector<String> userIds = new Vector<>(); for (int i = 0; i < getListView().getCount(); ++i) { if (getListView().isItemChecked(i)) { userIds.add(mAdapter.getUserId(i)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index 51fac4779..53986a392 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -17,7 +17,7 @@ package org.sufficientlysecure.keychain.ui; -import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.os.Build; @@ -27,19 +27,22 @@ import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.openpgp.PGPEncryptedData; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference; +import org.sufficientlysecure.keychain.util.Preferences; import java.util.List; -@SuppressLint("NewApi") -public class PreferencesActivity extends PreferenceActivity { +public class SettingsActivity extends PreferenceActivity { public static final String ACTION_PREFS_CLOUD = "org.sufficientlysecure.keychain.ui.PREFS_CLOUD"; public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV"; @@ -54,6 +57,8 @@ public class PreferencesActivity extends PreferenceActivity { sPreferences = Preferences.getPreferences(this); super.onCreate(savedInstanceState); + setupToolbar(); + String action = getIntent().getAction(); if (action != null && action.equals(ACTION_PREFS_CLOUD)) { @@ -64,9 +69,9 @@ public class PreferencesActivity extends PreferenceActivity { mKeyServerPreference .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { - Intent intent = new Intent(PreferencesActivity.this, - PreferencesKeyServerActivity.class); - intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS, + Intent intent = new Intent(SettingsActivity.this, + SettingsKeyServerActivity.class); + intent.putExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS, sPreferences.getKeyServers()); startActivityForResult(intent, REQUEST_CODE_KEYSERVER_PREF); return false; @@ -130,12 +135,33 @@ public class PreferencesActivity extends PreferenceActivity { initializeUseNumKeypadForYubikeyPin( (CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN)); - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { - // Load the legacy preferences headers - addPreferencesFromResource(R.xml.preference_headers_legacy); } } + /** + * Hack to get Toolbar in PreferenceActivity. See http://stackoverflow.com/a/26614696 + */ + private void setupToolbar() { + ViewGroup root = (ViewGroup) findViewById(android.R.id.content); + LinearLayout content = (LinearLayout) root.getChildAt(0); + LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.preference_toolbar_activity, null); + + root.removeAllViews(); + toolbarContainer.addView(content); + root.addView(toolbarContainer); + + Toolbar toolbar = (Toolbar) toolbarContainer.findViewById(R.id.toolbar); + toolbar.setTitle(R.string.title_preferences); + toolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp)); + toolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //What to do on back clicked + finish(); + } + }); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { @@ -144,7 +170,7 @@ public class PreferencesActivity extends PreferenceActivity { return; } String servers[] = data - .getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS); + .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS); sPreferences.setKeyServers(servers); mKeyServerPreference.setSummary(keyserverSummary(this)); break; @@ -157,7 +183,6 @@ public class PreferencesActivity extends PreferenceActivity { } } - /* Called only on Honeycomb and later */ @Override public void onBuildHeaders(List<Header> target) { super.onBuildHeaders(target); @@ -185,8 +210,8 @@ public class PreferencesActivity extends PreferenceActivity { .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { Intent intent = new Intent(getActivity(), - PreferencesKeyServerActivity.class); - intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS, + SettingsKeyServerActivity.class); + intent.putExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS, sPreferences.getKeyServers()); startActivityForResult(intent, REQUEST_CODE_KEYSERVER_PREF); return false; @@ -208,7 +233,7 @@ public class PreferencesActivity extends PreferenceActivity { return; } String servers[] = data - .getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS); + .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS); sPreferences.setKeyServers(servers); mKeyServerPreference.setSummary(keyserverSummary(getActivity())); break; @@ -287,6 +312,7 @@ public class PreferencesActivity extends PreferenceActivity { } } + @TargetApi(Build.VERSION_CODES.KITKAT) protected boolean isValidFragment(String fragmentName) { return AdvancedPrefsFragment.class.getName().equals(fragmentName) || CloudSearchPrefsFragment.class.getName().equals(fragmentName) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java index 2a8ef171c..080dc2495 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java @@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.ui; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -28,14 +27,13 @@ import android.view.ViewGroup; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.util.ActionBarHelper; import org.sufficientlysecure.keychain.ui.widget.Editor; import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor; import java.util.Vector; -public class PreferencesKeyServerActivity extends ActionBarActivity implements OnClickListener, +public class SettingsKeyServerActivity extends BaseActivity implements OnClickListener, EditorListener { public static final String EXTRA_KEY_SERVERS = "key_servers"; @@ -52,23 +50,19 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O super.onCreate(savedInstanceState); // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done, + setFullScreenDialogDoneClose(R.string.btn_save, new View.OnClickListener() { @Override public void onClick(View v) { - // ok okClicked(); } - }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() { + }, + new View.OnClickListener() { @Override public void onClick(View v) { - // cancel cancelClicked(); } - } - ); - - setContentView(R.layout.key_server_preference); + }); mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -102,6 +96,11 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O makeServerList(servers); } + @Override + protected void initLayout() { + setContentView(R.layout.key_server_preference); + } + private void makeServerList(String[] servers) { if (servers != null) { mEditors.removeAllViews(); @@ -137,7 +136,7 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O } private Vector<String> serverList() { - Vector<String> servers = new Vector<String>(); + Vector<String> servers = new Vector<>(); for (int i = 0; i < mEditors.getChildCount(); ++i) { KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i); String tmp = editor.getValue(); @@ -150,7 +149,7 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O private void okClicked() { Intent data = new Intent(); - Vector<String> servers = new Vector<String>(); + Vector<String> servers = new Vector<>(); for (int i = 0; i < mEditors.getChildCount(); ++i) { KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i); String tmp = editor.getValue(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index 497486a5e..e19793fd5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -24,7 +24,6 @@ import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.support.v4.app.NavUtils; -import android.support.v7.app.ActionBarActivity; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; @@ -34,17 +33,17 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; /** * Sends the selected public key to a keyserver */ -public class UploadKeyActivity extends ActionBarActivity { +public class UploadKeyActivity extends BaseActivity { private View mUploadButton; private Spinner mKeyServerSpinner; @@ -54,12 +53,10 @@ public class UploadKeyActivity extends ActionBarActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.upload_key_activity); - mUploadButton = findViewById(R.id.upload_key_action_upload); mKeyServerSpinner = (Spinner) findViewById(R.id.upload_key_keyserver); - ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, + ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, Preferences.getPreferences(this) .getKeyServers() ); @@ -86,6 +83,11 @@ public class UploadKeyActivity extends ActionBarActivity { } } + @Override + protected void initLayout() { + setContentView(R.layout.upload_key_activity); + } + private void uploadKey() { // Send all information needed to service to upload key in other thread Intent intent = new Intent(this, KeychainIntentService.class); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java index 34c08a6c7..a80503591 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java @@ -27,9 +27,6 @@ import android.support.v4.app.NavUtils; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; -import android.text.SpannableString; -import android.text.SpannableStringBuilder; import android.text.format.DateFormat; import android.view.MenuItem; import android.view.View; @@ -37,19 +34,18 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import java.util.Date; -public class ViewCertActivity extends ActionBarActivity +public class ViewCertActivity extends BaseActivity implements LoaderManager.LoaderCallbacks<Cursor> { // These are the rows that we will retrieve. @@ -86,8 +82,6 @@ public class ViewCertActivity extends ActionBarActivity ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.view_cert_activity); - mSigneeKey = (TextView) findViewById(R.id.signee_key); mSigneeUid = (TextView) findViewById(R.id.signee_uid); mAlgorithm = (TextView) findViewById(R.id.algorithm); @@ -113,6 +107,11 @@ public class ViewCertActivity extends ActionBarActivity } @Override + protected void initLayout() { + setContentView(R.layout.view_cert_activity); + } + + @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. 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 a7ba4accf..e1a8981c4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -19,9 +19,10 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; +import android.app.ActivityOptions; import android.content.Intent; import android.database.Cursor; -import android.graphics.PorterDuff; +import android.graphics.Bitmap; import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; @@ -33,41 +34,45 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.provider.ContactsContract; +import android.provider.Settings; +import android.support.v4.app.ActivityCompat; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; -import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.widget.CardView; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.animation.AlphaAnimation; +import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; +import com.getbase.floatingactionbutton.FloatingActionButton; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.ContactHelper; -import org.sufficientlysecure.keychain.util.ExportHelper; -import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; -import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; -import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout.TabColorizer; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; +import org.sufficientlysecure.keychain.ui.widget.AspectRatioImageView; +import org.sufficientlysecure.keychain.util.ContactHelper; +import org.sufficientlysecure.keychain.util.ExportHelper; +import org.sufficientlysecure.keychain.util.Log; import java.util.Date; import java.util.HashMap; -public class ViewKeyActivity extends ActionBarActivity implements +public class ViewKeyActivity extends BaseActivity implements LoaderManager.LoaderCallbacks<Cursor> { ExportHelper mExportHelper; @@ -75,20 +80,18 @@ public class ViewKeyActivity extends ActionBarActivity implements protected Uri mDataUri; - public static final String EXTRA_SELECTED_TAB = "selected_tab"; - public static final int TAB_MAIN = 0; - public static final int TAB_SHARE = 1; - public static final int TAB_TRUST = 2; - - // view - private ViewPager mViewPager; - private SlidingTabLayout mSlidingTabLayout; - private PagerTabStripAdapter mTabsAdapter; - - private LinearLayout mStatusLayout; + private TextView mName; private TextView mStatusText; private ImageView mStatusImage; - private View mStatusDivider; + private RelativeLayout mBigToolbar; + + private ImageButton mActionEncryptFile; + private ImageButton mActionEncryptText; + private ImageButton mActionNfc; + private FloatingActionButton mFab; + private AspectRatioImageView mPhoto; + private ImageView mQrCode; + private CardView mQrCodeLayout; // NFC private NfcAdapter mNfcAdapter; @@ -99,6 +102,10 @@ public class ViewKeyActivity extends ActionBarActivity implements private static final int LOADER_ID_UNIFIED = 0; + private boolean mIsSecret = false; + private boolean mHasEncrypt = false; + private boolean mIsVerified = false; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -106,39 +113,20 @@ public class ViewKeyActivity extends ActionBarActivity implements mExportHelper = new ExportHelper(this); mProviderHelper = new ProviderHelper(this); - // let the actionbar look like Android's contact app - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setIcon(android.R.color.transparent); - actionBar.setHomeButtonEnabled(true); - - setContentView(R.layout.view_key_activity); + setTitle(null); - mStatusLayout = (LinearLayout) findViewById(R.id.view_key_status_layout); - mStatusText = (TextView) findViewById(R.id.view_key_status_text); + mName = (TextView) findViewById(R.id.view_key_name); + mStatusText = (TextView) findViewById(R.id.view_key_status); mStatusImage = (ImageView) findViewById(R.id.view_key_status_image); - mStatusDivider = findViewById(R.id.view_key_status_divider); - - 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 0xFFAA66CC; - } - - @Override - public int getDividerColor(int position) { - return 0; - } - }); + mBigToolbar = (RelativeLayout) findViewById(R.id.toolbar_big); - int switchToTab = TAB_MAIN; - Intent intent = getIntent(); - if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) { - switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB); - } + mActionEncryptFile = (ImageButton) findViewById(R.id.view_key_action_encrypt_files); + mActionEncryptText = (ImageButton) findViewById(R.id.view_key_action_encrypt_text); + mActionNfc = (ImageButton) findViewById(R.id.view_key_action_nfc); + mFab = (FloatingActionButton) findViewById(R.id.fab); + mPhoto = (AspectRatioImageView) findViewById(R.id.view_key_photo); + mQrCode = (ImageView) findViewById(R.id.view_key_qr_code); + mQrCodeLayout = (CardView) findViewById(R.id.view_key_qr_code_layout); mDataUri = getIntent().getData(); if (mDataUri == null) { @@ -158,39 +146,77 @@ public class ViewKeyActivity extends ActionBarActivity implements Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + mActionEncryptFile.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + encrypt(mDataUri, false); + } + }); + mActionEncryptText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + encrypt(mDataUri, true); + } + }); + + mFab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mIsSecret) { + startSafeSlinger(mDataUri); + } else { + scanQrCode(); + } + } + }); + + mQrCodeLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showQrCodeDialog(); + } + }); + + mActionNfc.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + invokeNfcBeam(); + } + }); + + // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); initNfc(mDataUri); - initTabs(mDataUri); - - // switch to tab selected by extra - mViewPager.setCurrentItem(switchToTab); + startFragment(savedInstanceState, mDataUri); } - private void initTabs(Uri dataUri) { - mTabsAdapter = new PagerTabStripAdapter(this); - mViewPager.setAdapter(mTabsAdapter); - - Bundle mainBundle = new Bundle(); - mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri); - mTabsAdapter.addTab(ViewKeyMainFragment.class, - mainBundle, getString(R.string.key_view_tab_main)); + @Override + protected void initLayout() { + setContentView(R.layout.view_key_activity); + } - Bundle shareBundle = new Bundle(); - shareBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri); - mTabsAdapter.addTab(ViewKeyShareFragment.class, - shareBundle, getString(R.string.key_view_tab_share)); + private void startFragment(Bundle savedInstanceState, Uri dataUri) { + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; + } - Bundle trustBundle = new Bundle(); - trustBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri); - mTabsAdapter.addTab(ViewKeyTrustFragment.class, trustBundle, - getString(R.string.key_view_tab_trust)); + // Create an instance of the fragment + ViewKeyFragment frag = ViewKeyFragment.newInstance(dataUri); - // update layout after operations - mSlidingTabLayout.setViewPager(mViewPager); + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.view_key_fragment, frag) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); } @Override @@ -206,7 +232,7 @@ public class ViewKeyActivity extends ActionBarActivity implements try { switch (item.getItemId()) { case android.R.id.home: { - Intent homeIntent = new Intent(this, KeyListActivity.class); + Intent homeIntent = new Intent(this, MainActivity.class); homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(homeIntent); return true; @@ -220,9 +246,26 @@ public class ViewKeyActivity extends ActionBarActivity implements return true; } case R.id.menu_key_view_advanced: { - Intent advancedIntent = new Intent(this, ViewKeyAdvancedActivity.class); + Intent advancedIntent = new Intent(this, ViewKeyAdvActivity.class); advancedIntent.setData(mDataUri); startActivity(advancedIntent); + return true; + } + case R.id.menu_key_view_refresh: { + try { + updateFromKeyserver(mDataUri, mProviderHelper); + } catch (ProviderHelper.NotFoundException e) { + Notify.showNotify(this, R.string.error_key_not_found, Notify.Style.ERROR); + } + return true; + } + case R.id.menu_key_view_edit: { + editKey(mDataUri); + return true; + } + case R.id.menu_key_view_certify_fingerprint: { + certifyFingeprint(mDataUri); + return true; } } } catch (ProviderHelper.NotFoundException e) { @@ -232,6 +275,75 @@ public class ViewKeyActivity extends ActionBarActivity implements return super.onOptionsItemSelected(item); } + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem editKey = menu.findItem(R.id.menu_key_view_edit); + editKey.setVisible(mIsSecret); + MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint); + certifyFingerprint.setVisible(!mIsSecret && !mIsVerified); + + return true; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private void invokeNfcBeam() { + // Check for available NFC Adapter + mNfcAdapter = NfcAdapter.getDefaultAdapter(this); + if (mNfcAdapter == null || !mNfcAdapter.isEnabled()) { + Notify.createNotify(this, R.string.error_nfc_needed, Notify.LENGTH_LONG, Notify.Style.ERROR, new Notify.ActionListener() { + @Override + public void onAction() { + Intent intentSettings = new Intent(Settings.ACTION_NFC_SETTINGS); + startActivity(intentSettings); + } + }, R.string.menu_nfc_preferences).show(); + + return; + } + + if (!mNfcAdapter.isNdefPushEnabled()) { + Notify.createNotify(this, R.string.error_beam_needed, Notify.LENGTH_LONG, Notify.Style.ERROR, new Notify.ActionListener() { + @Override + public void onAction() { + Intent intentSettings = new Intent(Settings.ACTION_NFCSHARING_SETTINGS); + startActivity(intentSettings); + } + }, R.string.menu_beam_preferences).show(); + + return; + } + + mNfcAdapter.invokeBeam(this); + } + + private void scanQrCode() { + Intent scanQrCode = new Intent(this, QrCodeScanActivity.class); + scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT); + startActivityForResult(scanQrCode, 0); + } + + private void certifyFingeprint(Uri dataUri) { + Intent intent = new Intent(this, CertifyFingerprintActivity.class); + intent.setData(dataUri); + startActivityForResult(intent, 0); + } + + private void showQrCodeDialog() { + Intent qrCodeIntent = new Intent(this, QrCodeViewActivity.class); + + // create the transition animation - the images in the layouts + // of both activities are defined with android:transitionName="qr_code" + Bundle opts = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ActivityOptions options = ActivityOptions + .makeSceneTransitionAnimation(this, mQrCodeLayout, "qr_code"); + opts = options.toBundle(); + } + + qrCodeIntent.setData(mDataUri); + ActivityCompat.startActivity(this, qrCodeIntent, opts); + } + private void exportToFile(Uri dataUri, ExportHelper exportHelper, ProviderHelper providerHelper) throws ProviderHelper.NotFoundException { Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri); @@ -273,6 +385,101 @@ public class ViewKeyActivity extends ActionBarActivity implements } } + private void encrypt(Uri dataUri, boolean text) { + // If there is no encryption key, don't bother. + if (!mHasEncrypt) { + Notify.showNotify(this, R.string.error_no_encrypt_subkey, Notify.Style.ERROR); + return; + } + try { + long keyId = new ProviderHelper(this) + .getCachedPublicKeyRing(dataUri) + .extractOrGetMasterKeyId(); + long[] encryptionKeyIds = new long[]{keyId}; + Intent intent; + if (text) { + intent = new Intent(this, EncryptTextActivity.class); + intent.setAction(EncryptTextActivity.ACTION_ENCRYPT_TEXT); + intent.putExtra(EncryptTextActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); + } else { + intent = new Intent(this, EncryptFilesActivity.class); + intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); + intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); + } + // used instead of startActivity set actionbar based on callingPackage + startActivityForResult(intent, 0); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "key not found!", e); + } + } + + private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper) + throws ProviderHelper.NotFoundException { + byte[] blob = (byte[]) providerHelper.getGenericData( + KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri), + KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); + + Intent queryIntent = new Intent(this, ImportKeysActivity.class); + queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT); + queryIntent.putExtra(ImportKeysActivity.EXTRA_FINGERPRINT, fingerprint); + + startActivityForResult(queryIntent, 0); + } + + private void editKey(Uri dataUri) { + Intent editIntent = new Intent(this, EditKeyActivity.class); + editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri)); + startActivityForResult(editIntent, 0); + } + + private void startSafeSlinger(Uri dataUri) { + long keyId = 0; + try { + keyId = new ProviderHelper(this) + .getCachedPublicKeyRing(dataUri) + .extractOrGetMasterKeyId(); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "key not found!", e); + } + Intent safeSlingerIntent = new Intent(this, SafeSlingerActivity.class); + safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, keyId); + startActivityForResult(safeSlingerIntent, 0); + } + + + /** + * Load QR Code asynchronously and with a fade in animation + * + * @param fingerprint + */ + private void loadQrCode(final String fingerprint) { + AsyncTask<Void, Void, Bitmap> loadTask = + new AsyncTask<Void, Void, Bitmap>() { + protected Bitmap doInBackground(Void... unused) { + String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; + // render with minimal size + return QrCodeUtils.getQRCodeBitmap(qrCodeContent, 0); + } + + protected void onPostExecute(Bitmap qrCode) { + // scale the image up to our actual size. we do this in code rather + // than let the ImageView do this because we don't require filtering. + Bitmap scaled = Bitmap.createScaledBitmap(qrCode, + mQrCode.getHeight(), mQrCode.getHeight(), + false); + mQrCode.setImageBitmap(scaled); + + // simple fade-in animation + AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); + anim.setDuration(200); + mQrCode.startAnimation(anim); + } + }; + + loadTask.execute(); + } + /** * NFC: Initialize NFC sharing if OS and device supports it */ @@ -363,25 +570,34 @@ public class ViewKeyActivity extends ActionBarActivity implements } }; - static final String[] UNIFIED_PROJECTION = new String[]{ + // These are the rows that we will retrieve. + static final String[] PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.IS_REVOKED, KeychainContract.KeyRings.EXPIRY, - + KeychainContract.KeyRings.VERIFIED, + KeychainContract.KeyRings.HAS_ANY_SECRET, + KeychainContract.KeyRings.FINGERPRINT, + KeychainContract.KeyRings.HAS_ENCRYPT }; - static final int INDEX_UNIFIED_MASTER_KEY_ID = 1; - static final int INDEX_UNIFIED_USER_ID = 2; - static final int INDEX_UNIFIED_IS_REVOKED = 3; - static final int INDEX_UNIFIED_EXPIRY = 4; + + static final int INDEX_MASTER_KEY_ID = 1; + static final int INDEX_USER_ID = 2; + static final int INDEX_IS_REVOKED = 3; + static final int INDEX_EXPIRY = 4; + static final int INDEX_VERIFIED = 5; + static final int INDEX_HAS_ANY_SECRET = 6; + static final int INDEX_FINGERPRINT = 7; + static final int INDEX_HAS_ENCRYPT = 8; @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_UNIFIED: { Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); - return new CursorLoader(this, baseUri, UNIFIED_PROJECTION, null, null, null); + return new CursorLoader(this, baseUri, PROJECTION, null, null, null); } default: @@ -404,36 +620,136 @@ public class ViewKeyActivity extends ActionBarActivity implements case LOADER_ID_UNIFIED: { if (data.moveToFirst()) { // get name, email, and comment from USER_ID - String[] mainUserId = KeyRing.splitUserId(data.getString(INDEX_UNIFIED_USER_ID)); + String[] mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID)); if (mainUserId[0] != null) { - setTitle(mainUserId[0]); + mName.setText(mainUserId[0]); } else { - setTitle(R.string.user_id_no_name); + mName.setText(R.string.user_id_no_name); } - // get key id from MASTER_KEY_ID - long masterKeyId = data.getLong(INDEX_UNIFIED_MASTER_KEY_ID); - getSupportActionBar().setSubtitle(KeyFormattingUtils.beautifyKeyIdWithPrefix(this, masterKeyId)); + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT)); + + mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0; + boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; + boolean isExpired = !data.isNull(INDEX_EXPIRY) + && new Date(data.getLong(INDEX_EXPIRY) * 1000).before(new Date()); + mIsVerified = data.getInt(INDEX_VERIFIED) > 0; - boolean isRevoked = data.getInt(INDEX_UNIFIED_IS_REVOKED) > 0; - boolean isExpired = !data.isNull(INDEX_UNIFIED_EXPIRY) - && new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000).before(new Date()); + // re-create options menu based on mIsSecret, mIsVerified + supportInvalidateOptionsMenu(); + + AsyncTask<String, Void, Bitmap> photoTask = + new AsyncTask<String, Void, Bitmap>() { + protected Bitmap doInBackground(String... fingerprint) { + return ContactHelper.photoFromFingerprint(getContentResolver(), fingerprint[0]); + } + + protected void onPostExecute(Bitmap photo) { + mPhoto.setImageBitmap(photo); + mPhoto.setVisibility(View.VISIBLE); + } + }; // Note: order is important + int color; if (isRevoked) { mStatusText.setText(R.string.view_key_revoked); - KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, KeyFormattingUtils.STATE_REVOKED); - mStatusDivider.setVisibility(View.VISIBLE); - mStatusLayout.setVisibility(View.VISIBLE); + mStatusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, + KeyFormattingUtils.STATE_REVOKED, R.color.icons, true); + color = getResources().getColor(R.color.android_red_light); + + mActionEncryptFile.setVisibility(View.GONE); + mActionEncryptText.setVisibility(View.GONE); + mActionNfc.setVisibility(View.GONE); + mFab.setVisibility(View.GONE); + mQrCodeLayout.setVisibility(View.GONE); } else if (isExpired) { - mStatusText.setText(R.string.view_key_expired); - KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, KeyFormattingUtils.STATE_EXPIRED); - mStatusDivider.setVisibility(View.VISIBLE); - mStatusLayout.setVisibility(View.VISIBLE); + if (mIsSecret) { + mStatusText.setText(R.string.view_key_expired_secret); + } else { + mStatusText.setText(R.string.view_key_expired); + } + mStatusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, + KeyFormattingUtils.STATE_EXPIRED, R.color.icons, true); + color = getResources().getColor(R.color.android_red_light); + + mActionEncryptFile.setVisibility(View.GONE); + mActionEncryptText.setVisibility(View.GONE); + mActionNfc.setVisibility(View.GONE); + mFab.setVisibility(View.GONE); + mQrCodeLayout.setVisibility(View.GONE); + } else if (mIsSecret) { + mStatusText.setText(R.string.view_key_my_key); + mStatusImage.setVisibility(View.GONE); + color = getResources().getColor(R.color.primary); + photoTask.execute(fingerprint); + loadQrCode(fingerprint); + mQrCodeLayout.setVisibility(View.VISIBLE); + + // and place leftOf qr code + RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams) + mName.getLayoutParams(); + // remove right margin + nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + nameParams.setMarginEnd(0); + } + nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); + mName.setLayoutParams(nameParams); + + RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams) + mStatusText.getLayoutParams(); + statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + statusParams.setMarginEnd(0); + } + statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); + mStatusText.setLayoutParams(statusParams); + + mActionEncryptFile.setVisibility(View.VISIBLE); + mActionEncryptText.setVisibility(View.VISIBLE); + + // invokeBeam is available from API 21 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mActionNfc.setVisibility(View.VISIBLE); + } else { + mActionNfc.setVisibility(View.GONE); + } + mFab.setVisibility(View.VISIBLE); + mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); } else { - mStatusDivider.setVisibility(View.GONE); - mStatusLayout.setVisibility(View.GONE); + mActionEncryptFile.setVisibility(View.VISIBLE); + mActionEncryptText.setVisibility(View.VISIBLE); + mQrCodeLayout.setVisibility(View.GONE); + mActionNfc.setVisibility(View.GONE); + + if (mIsVerified) { + mStatusText.setText(R.string.view_key_verified); + mStatusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, + KeyFormattingUtils.STATE_VERIFIED, R.color.icons, true); + color = getResources().getColor(R.color.primary); + photoTask.execute(fingerprint); + + mFab.setVisibility(View.GONE); + } else { + mStatusText.setText(R.string.view_key_unverified); + mStatusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, + KeyFormattingUtils.STATE_UNVERIFIED, R.color.icons, true); + color = getResources().getColor(R.color.android_orange_light); + + mFab.setVisibility(View.VISIBLE); + } } + mToolbar.setBackgroundColor(color); + mStatusBar.setBackgroundColor(color); + mBigToolbar.setBackgroundColor(color); + + mStatusImage.setAlpha(80); break; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java new file mode 100644 index 000000000..894529cc5 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2015 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.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.ContactsContract; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.view.ViewPager; +import android.view.View; +import android.widget.Toast; + +import com.astuetz.PagerSlidingTabStrip; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ContactHelper; +import org.sufficientlysecure.keychain.util.ExportHelper; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Date; + +public class ViewKeyAdvActivity extends BaseActivity implements + LoaderManager.LoaderCallbacks<Cursor> { + + ExportHelper mExportHelper; + ProviderHelper mProviderHelper; + + protected Uri mDataUri; + + public static final String EXTRA_SELECTED_TAB = "selected_tab"; + public static final int TAB_MAIN = 0; + public static final int TAB_SHARE = 1; + + // view + private ViewPager mViewPager; + private PagerSlidingTabStrip mSlidingTabLayout; + private PagerTabStripAdapter mTabsAdapter; + + private static final int LOADER_ID_UNIFIED = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setFullScreenDialogClose(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + + mExportHelper = new ExportHelper(this); + mProviderHelper = new ProviderHelper(this); + + mViewPager = (ViewPager) findViewById(R.id.view_key_pager); + mSlidingTabLayout = (PagerSlidingTabStrip) findViewById(R.id.view_key_sliding_tab_layout); + + int switchToTab = TAB_MAIN; + Intent intent = getIntent(); + if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) { + switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB); + } + + mDataUri = getIntent().getData(); + if (mDataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be uri of key!"); + finish(); + return; + } + if (mDataUri.getHost().equals(ContactsContract.AUTHORITY)) { + mDataUri = ContactHelper.dataUriFromContactUri(this, mDataUri); + if (mDataUri == null) { + Log.e(Constants.TAG, "Contact Data missing. Should be uri of key!"); + Toast.makeText(this, R.string.error_contacts_key_id_missing, Toast.LENGTH_LONG).show(); + finish(); + return; + } + } + + Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + + // Prepare the loaders. Either re-connect with an existing ones, + // or start new ones. + getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); + + initTabs(mDataUri); + + // switch to tab selected by extra + mViewPager.setCurrentItem(switchToTab); + } + + @Override + protected void initLayout() { + setContentView(R.layout.view_key_adv_activity); + } + + private void initTabs(Uri dataUri) { + mTabsAdapter = new PagerTabStripAdapter(this); + mViewPager.setAdapter(mTabsAdapter); + + Bundle mainBundle = new Bundle(); + mainBundle.putParcelable(ViewKeyAdvMainFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ViewKeyAdvMainFragment.class, + mainBundle, getString(R.string.key_view_tab_main)); + + Bundle shareBundle = new Bundle(); + shareBundle.putParcelable(ViewKeyAdvMainFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ViewKeyAdvShareFragment.class, + shareBundle, getString(R.string.key_view_tab_share)); + + Bundle keysBundle = new Bundle(); + keysBundle.putParcelable(ViewKeyAdvSubkeysFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ViewKeyAdvSubkeysFragment.class, + keysBundle, getString(R.string.key_view_tab_keys)); + + Bundle certsBundle = new Bundle(); + certsBundle.putParcelable(ViewKeyAdvCertsFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ViewKeyAdvCertsFragment.class, + certsBundle, getString(R.string.key_view_tab_certs)); + + Bundle trustBundle = new Bundle(); + trustBundle.putParcelable(ViewKeyTrustFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ViewKeyTrustFragment.class, + trustBundle, getString(R.string.key_view_tab_keybase)); + + // update layout after operations + mSlidingTabLayout.setViewPager(mViewPager); + } + + // These are the rows that we will retrieve. + static final String[] PROJECTION = new String[]{ + KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.IS_REVOKED, + KeychainContract.KeyRings.EXPIRY, + KeychainContract.KeyRings.VERIFIED, + KeychainContract.KeyRings.HAS_ANY_SECRET + }; + + static final int INDEX_MASTER_KEY_ID = 1; + static final int INDEX_USER_ID = 2; + static final int INDEX_IS_REVOKED = 3; + static final int INDEX_EXPIRY = 4; + static final int INDEX_VERIFIED = 5; + static final int INDEX_HAS_ANY_SECRET = 6; + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_ID_UNIFIED: { + Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); + return new CursorLoader(this, baseUri, PROJECTION, null, null, null); + } + + default: + return null; + } + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + /* TODO better error handling? May cause problems when a key is deleted, + * because the notification triggers faster than the activity closes. + */ + // Avoid NullPointerExceptions... + if (data.getCount() == 0) { + return; + } + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + switch (loader.getId()) { + case LOADER_ID_UNIFIED: { + if (data.moveToFirst()) { + // get name, email, and comment from USER_ID + String[] mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID)); + if (mainUserId[0] != null) { + setTitle(mainUserId[0]); + } else { + setTitle(R.string.user_id_no_name); + } + + // get key id from MASTER_KEY_ID + long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); + getSupportActionBar().setSubtitle(KeyFormattingUtils.beautifyKeyIdWithPrefix(this, masterKeyId)); + + boolean isSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; + boolean isExpired = !data.isNull(INDEX_EXPIRY) + && new Date(data.getLong(INDEX_EXPIRY) * 1000).before(new Date()); + boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; + + // Note: order is important + int color; + if (isRevoked || isExpired) { + color = getResources().getColor(R.color.android_red_light); + } else if (isSecret) { + color = getResources().getColor(R.color.primary); + } else { + if (isVerified) { + color = getResources().getColor(R.color.primary); + } else { + color = getResources().getColor(R.color.android_orange_light); + } + } + mToolbar.setBackgroundColor(color); + mStatusBar.setBackgroundColor(color); + mSlidingTabLayout.setBackgroundColor(color); + + break; + } + } + } + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // if a result has been returned, display a notify + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(this).show(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvancedFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java index 9c37f2d94..90d7a400f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvancedFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014-2015 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 @@ -30,7 +30,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.ListView; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; @@ -39,7 +38,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; -import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -47,23 +45,16 @@ import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; -public class ViewKeyAdvancedFragment extends LoaderFragment implements +public class ViewKeyAdvCertsFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener { public static final String ARG_DATA_URI = "data_uri"; - private ListView mSubkeysList; - private SubkeysAdapter mSubkeysAdapter; - private StickyListHeadersListView mStickyList; private CertListAdapter mCertsAdapter; - private Uri mDataUriSubkeys; private Uri mDataUriCerts; - private static final int LOADER_SUBKEYS = 1; - private static final int LOADER_CERTS = 2; - // These are the rows that we will retrieve. static final String[] CERTS_PROJECTION = new String[]{ KeychainContract.Certs._ID, @@ -80,14 +71,14 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements static final String CERTS_SORT_ORDER = KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.RANK + " ASC, " + KeychainContract.Certs.VERIFIED + " DESC, " - + KeychainContract.Certs.TYPE + " DESC, " + + KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.TYPE + " DESC, " + KeychainContract.Certs.SIGNER_UID + " ASC"; /** * Creates new instance of this fragment */ - public static ViewKeyAdvancedFragment newInstance(Uri dataUri) { - ViewKeyAdvancedFragment frag = new ViewKeyAdvancedFragment(); + public static ViewKeyAdvCertsFragment newInstance(Uri dataUri) { + ViewKeyAdvCertsFragment frag = new ViewKeyAdvCertsFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DATA_URI, dataUri); @@ -99,9 +90,8 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.view_key_advanced_fragment, getContainer()); + View view = inflater.inflate(R.layout.view_key_adv_certs_fragment, getContainer()); - mSubkeysList = (ListView) view.findViewById(R.id.keys); mStickyList = (StickyListHeadersListView) view.findViewById(R.id.certs_list); return root; @@ -122,7 +112,6 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements } private void loadData(Uri dataUri) { - mDataUriSubkeys = KeychainContract.Keys.buildKeysUri(dataUri); mDataUriCerts = KeychainContract.Certs.buildCertsUri(dataUri); mStickyList.setAreHeadersSticky(true); @@ -132,34 +121,23 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements mStickyList.setEmptyView(getActivity().findViewById(R.id.empty)); // Create an empty adapter we will use to display the loaded data. - mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0); - mSubkeysList.setAdapter(mSubkeysAdapter); - mCertsAdapter = new CertListAdapter(getActivity(), null); mStickyList.setAdapter(mCertsAdapter); // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. - getLoaderManager().initLoader(LOADER_SUBKEYS, null, this); - getLoaderManager().initLoader(LOADER_CERTS, null, this); + getLoaderManager().initLoader(0, null, this); } public Loader<Cursor> onCreateLoader(int id, Bundle args) { setContentShown(false); - switch (id) { - case LOADER_SUBKEYS: - return new CursorLoader(getActivity(), mDataUriSubkeys, - SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null); - - case LOADER_CERTS: - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), mDataUriCerts, - CERTS_PROJECTION, null, null, CERTS_SORT_ORDER); - - default: - return null; - } + + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getActivity(), mDataUriCerts, + CERTS_PROJECTION, null, null, CERTS_SORT_ORDER); + } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { @@ -170,15 +148,8 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) - switch (loader.getId()) { - case LOADER_SUBKEYS: - mSubkeysAdapter.swapCursor(data); - break; - case LOADER_CERTS: - mCertsAdapter.swapCursor(data); - mStickyList.setAdapter(mCertsAdapter); - break; - } + mCertsAdapter.swapCursor(data); + mStickyList.setAdapter(mCertsAdapter); // TODO: maybe show not before both are loaded! setContentShown(true); @@ -189,14 +160,7 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader<Cursor> loader) { - switch (loader.getId()) { - case LOADER_SUBKEYS: - mSubkeysAdapter.swapCursor(null); - break; - case LOADER_CERTS: - mCertsAdapter.swapCursor(null); - break; - } + mCertsAdapter.swapCursor(null); } /** @@ -307,7 +271,7 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.view_key_certs_item, parent, false); + return mInflater.inflate(R.layout.view_key_adv_certs_item, parent, false); } /** @@ -322,7 +286,7 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements HeaderViewHolder holder; if (convertView == null) { holder = new HeaderViewHolder(); - convertView = mInflater.inflate(R.layout.view_key_certs_header, parent, false); + convertView = mInflater.inflate(R.layout.view_key_adv_certs_header, parent, false); holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text); holder.count = (TextView) convertView.findViewById(R.id.certs_num); convertView.setTag(holder); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvMainFragment.java index f1453c40c..c9d20f9f4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvMainFragment.java @@ -37,20 +37,20 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.Log; import java.util.Date; -public class ViewKeyMainFragment extends LoaderFragment implements +public class ViewKeyAdvMainFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks<Cursor> { public static final String ARG_DATA_URI = "uri"; @@ -80,7 +80,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.view_key_main_fragment, getContainer()); + View view = inflater.inflate(R.layout.view_key_adv_main_fragment, getContainer()); mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); mActionEdit = view.findViewById(R.id.view_key_action_edit); @@ -199,7 +199,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); } case LOADER_ID_USER_IDS: { - Uri baseUri = UserIds.buildUserIdsUri(mDataUri); + Uri baseUri = UserPackets.buildUserIdsUri(mDataUri); return new CursorLoader(getActivity(), baseUri, UserIdsAdapter.USER_IDS_PROJECTION, null, null, null); } @@ -265,9 +265,10 @@ public class ViewKeyMainFragment extends LoaderFragment implements } } - case LOADER_ID_USER_IDS: + case LOADER_ID_USER_IDS: { mUserIdsAdapter.swapCursor(data); break; + } } setContentShown(true); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index aa260b654..6208cff4e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -56,7 +56,7 @@ import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; -public class ViewKeyShareFragment extends LoaderFragment implements +public class ViewKeyAdvShareFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks<Cursor> { public static final String ARG_DATA_URI = "uri"; @@ -81,9 +81,9 @@ public class ViewKeyShareFragment extends LoaderFragment implements @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.view_key_share_fragment, getContainer()); + View view = inflater.inflate(R.layout.view_key_adv_share_fragment, getContainer()); - mProviderHelper = new ProviderHelper(ViewKeyShareFragment.this.getActivity()); + mProviderHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity()); mFingerprint = (TextView) view.findViewById(R.id.view_key_fingerprint); mFingerprintQrCode = (ImageView) view.findViewById(R.id.view_key_fingerprint_qr_code_image); @@ -228,10 +228,7 @@ public class ViewKeyShareFragment extends LoaderFragment implements } startActivity(Intent.createChooser(sendIntent, title)); } - } catch (PgpGeneralException e) { - Log.e(Constants.TAG, "error processing key!", e); - Notify.showNotify(getActivity(), R.string.error_key_processing, Notify.Style.ERROR); - } catch (IOException e) { + } catch (PgpGeneralException | IOException e) { Log.e(Constants.TAG, "error processing key!", e); Notify.showNotify(getActivity(), R.string.error_key_processing, Notify.Style.ERROR); } catch (ProviderHelper.NotFoundException e) { @@ -361,7 +358,7 @@ public class ViewKeyShareFragment extends LoaderFragment implements protected void onPostExecute(Bitmap qrCode) { // only change view, if fragment is attached to activity - if (ViewKeyShareFragment.this.isAdded()) { + if (ViewKeyAdvShareFragment.this.isAdded()) { // scale the image up to our actual size. we do this in code rather // than let the ImageView do this because we don't require filtering. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java new file mode 100644 index 000000000..bd00c6780 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2014-2015 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.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; +import org.sufficientlysecure.keychain.util.Log; + +public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements + LoaderManager.LoaderCallbacks<Cursor> { + + public static final String ARG_DATA_URI = "data_uri"; + + private ListView mSubkeysList; + private SubkeysAdapter mSubkeysAdapter; + + private Uri mDataUriSubkeys; + + /** + * Creates new instance of this fragment + */ + public static ViewKeyAdvSubkeysFragment newInstance(Uri dataUri) { + ViewKeyAdvSubkeysFragment frag = new ViewKeyAdvSubkeysFragment(); + + Bundle args = new Bundle(); + args.putParcelable(ARG_DATA_URI, dataUri); + + frag.setArguments(args); + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.view_key_adv_subkeys_fragment, getContainer()); + + mSubkeysList = (ListView) view.findViewById(R.id.keys); + + return root; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + getActivity().finish(); + return; + } + + loadData(dataUri); + } + + private void loadData(Uri dataUri) { + mDataUriSubkeys = KeychainContract.Keys.buildKeysUri(dataUri); + + // Create an empty adapter we will use to display the loaded data. + mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0); + mSubkeysList.setAdapter(mSubkeysAdapter); + + // Prepare the loaders. Either re-connect with an existing ones, + // or start new ones. + getLoaderManager().initLoader(0, null, this); + } + + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + setContentShown(false); + + return new CursorLoader(getActivity(), mDataUriSubkeys, + SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null); + } + + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Avoid NullPointerExceptions, if we get an empty result set. + if (data.getCount() == 0) { + return; + } + + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mSubkeysAdapter.swapCursor(data); + + // TODO: maybe show not before both are loaded! + setContentShown(true); + } + + /** + * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. + * We need to make sure we are no longer using it. + */ + public void onLoaderReset(Loader<Cursor> loader) { + mSubkeysAdapter.swapCursor(null); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java new file mode 100644 index 000000000..453bfd499 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; +import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Date; + +public class ViewKeyFragment extends LoaderFragment implements + LoaderManager.LoaderCallbacks<Cursor> { + + public static final String ARG_DATA_URI = "uri"; + + private ListView mUserIds; + + boolean mIsSecret = false; + + private static final int LOADER_ID_UNIFIED = 0; + private static final int LOADER_ID_USER_IDS = 1; + + private UserIdsAdapter mUserIdsAdapter; + + private Uri mDataUri; + + ProviderHelper mProviderHelper; + + /** + * Creates new instance of this fragment + */ + public static ViewKeyFragment newInstance(Uri dataUri) { + ViewKeyFragment frag = new ViewKeyFragment(); + Bundle args = new Bundle(); + args.putParcelable(ARG_DATA_URI, dataUri); + + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.view_key_fragment, getContainer()); + + mProviderHelper = new ProviderHelper(getActivity()); + + mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + + mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + showUserIdInfo(position); + } + }); + + return root; + } + + private void showUserIdInfo(final int position) { + if (!mIsSecret) { + final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); + final int isVerified = mUserIdsAdapter.getIsVerified(position); + + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + UserIdInfoDialogFragment dialogFragment = + UserIdInfoDialogFragment.newInstance(isRevoked, isVerified); + + dialogFragment.show(getActivity().getSupportFragmentManager(), "userIdInfoDialog"); + } + }); + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + getActivity().finish(); + return; + } + + loadData(dataUri); + } + + + // These are the rows that we will retrieve. + static final String[] UNIFIED_PROJECTION = new String[]{ + KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.IS_REVOKED, + KeychainContract.KeyRings.EXPIRY, + KeychainContract.KeyRings.VERIFIED, + KeychainContract.KeyRings.HAS_ANY_SECRET, + KeychainContract.KeyRings.FINGERPRINT, + KeychainContract.KeyRings.HAS_ENCRYPT + }; + + static final int INDEX_MASTER_KEY_ID = 1; + static final int INDEX_USER_ID = 2; + static final int INDEX_IS_REVOKED = 3; + static final int INDEX_EXPIRY = 4; + static final int INDEX_VERIFIED = 5; + static final int INDEX_HAS_ANY_SECRET = 6; + static final int INDEX_FINGERPRINT = 7; + static final int INDEX_HAS_ENCRYPT = 8; + + private void loadData(Uri dataUri) { + mDataUri = dataUri; + + Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + + // Prepare the loaders. Either re-connect with an existing ones, + // or start new ones. + getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); + } + + // don't show revoked user ids here, irrelevant for average users + public static final String USER_IDS_WHERE = UserPackets.IS_REVOKED + " = 0"; + + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + setContentShown(false); + + switch (id) { + case LOADER_ID_UNIFIED: { + Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); + } + case LOADER_ID_USER_IDS: { + Uri baseUri = UserPackets.buildUserIdsUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, + UserIdsAdapter.USER_IDS_PROJECTION, USER_IDS_WHERE, null, null); + } + + default: + return null; + } + } + + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + /* TODO better error handling? May cause problems when a key is deleted, + * because the notification triggers faster than the activity closes. + */ + // Avoid NullPointerExceptions... + if (data.getCount() == 0) { + return; + } + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + switch (loader.getId()) { + case LOADER_ID_UNIFIED: { + if (data.moveToFirst()) { + + mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + boolean hasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0; + boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; + boolean isExpired = !data.isNull(INDEX_EXPIRY) + && new Date(data.getLong(INDEX_EXPIRY) * 1000).before(new Date()); + boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; + + // load user ids after we know if it's a secret key + mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, false, !mIsSecret, null); + mUserIds.setAdapter(mUserIdsAdapter); + getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); + + break; + } + } + + case LOADER_ID_USER_IDS: { + mUserIdsAdapter.swapCursor(data); + break; + } + + } + setContentShown(true); + } + + /** + * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. + * We need to make sure we are no longer using it. + */ + public void onLoaderReset(Loader<Cursor> loader) { + switch (loader.getId()) { + case LOADER_ID_USER_IDS: { + mUserIdsAdapter.swapCursor(null); + break; + } + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java index 677646441..5483d16b8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java @@ -62,6 +62,8 @@ import java.util.List; public class ViewKeyTrustFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks<Cursor> { + public static final String ARG_DATA_URI = "uri"; + private View mStartSearch; private TextView mTrustReadout; private TextView mReportHeader; @@ -100,7 +102,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - Uri dataUri = getArguments().getParcelable(ViewKeyMainFragment.ARG_DATA_URI); + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); getActivity().finish(); 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 f3dce5823..6ba9e26ad 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 @@ -95,8 +95,8 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { * @see org.sufficientlysecure.keychain.operations.ImportExportOperation */ public ArrayList<ImportKeysListEntry> getSelectedEntries() { - ArrayList<ImportKeysListEntry> result = new ArrayList<ImportKeysListEntry>(); - ArrayList<ImportKeysListEntry> secrets = new ArrayList<ImportKeysListEntry>(); + ArrayList<ImportKeysListEntry> result = new ArrayList<>(); + ArrayList<ImportKeysListEntry> secrets = new ArrayList<>(); if (mData == null) { return result; } @@ -175,9 +175,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { } if (entry.isRevoked()) { - KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, KeyFormattingUtils.STATE_REVOKED, true); + KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); } else if (entry.isExpired()) { - KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, KeyFormattingUtils.STATE_EXPIRED, true); + KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray); } if (entry.isRevoked() || entry.isExpired()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java index 176c3ff5b..4781864dd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java @@ -21,13 +21,13 @@ import android.content.Context; import android.support.v4.content.AsyncTaskLoader; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.keyimport.CloudSearch; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.keyimport.CloudSearch; -import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; @@ -39,7 +39,7 @@ public class ImportKeysListCloudLoader Preferences.CloudSearchPrefs mCloudPrefs; String mServerQuery; - private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<ImportKeysListEntry>(); + private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<>(); private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper; public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) { @@ -51,7 +51,7 @@ public class ImportKeysListCloudLoader @Override public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() { - mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null); + mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, null); if (mServerQuery == null) { Log.e(Constants.TAG, "mServerQuery is null!"); @@ -119,7 +119,7 @@ public class ImportKeysListCloudLoader mEntryList.addAll(searchResult); } GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_OK, null); - mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, getKeyResult); + mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, getKeyResult); } catch (Keyserver.CloudSearchFailureException e) { // convert exception to result parcel int error = GetKeyResult.RESULT_ERROR; @@ -140,7 +140,7 @@ public class ImportKeysListCloudLoader OperationResult.OperationLog log = new OperationResult.OperationLog(); log.add(logType, 0); GetKeyResult getKeyResult = new GetKeyResult(error, log); - mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, getKeyResult); + mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, getKeyResult); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java index 41c7e0cbc..5447c5f96 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 @@ -24,9 +24,10 @@ import android.support.v4.util.LongSparseArray; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PositionAwareInputStream; @@ -34,28 +35,15 @@ import org.sufficientlysecure.keychain.util.PositionAwareInputStream; import java.io.BufferedInputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.Iterator; public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { - public static class NonPgpPartException extends Exception { - private int mCount; - - public NonPgpPartException(int count) { - this.mCount = count; - } - - public int getCount() { - return mCount; - } - } - final Context mContext; final InputData mInputData; - ArrayList<ImportKeysListEntry> mData = new ArrayList<ImportKeysListEntry>(); - LongSparseArray<ParcelableKeyRing> mParcelableRings = new LongSparseArray<ParcelableKeyRing>(); + ArrayList<ImportKeysListEntry> mData = new ArrayList<>(); + LongSparseArray<ParcelableKeyRing> mParcelableRings = new LongSparseArray<>(); AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper; public ImportKeysListLoader(Context context, InputData inputData) { @@ -72,7 +60,7 @@ public class ImportKeysListLoader } GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_OK, null); - mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mData, getKeyResult); + mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult); if (mInputData == null) { Log.e(Constants.TAG, "Input data is null!"); @@ -127,7 +115,7 @@ public class ImportKeysListLoader BufferedInputStream bufferedInput = new BufferedInputStream(progressIn); try { // parse all keyrings - Iterator<UncachedKeyRing> it = UncachedKeyRing.fromStream(bufferedInput); + IteratorWithIOThrow<UncachedKeyRing> it = UncachedKeyRing.fromStream(bufferedInput); while (it.hasNext()) { UncachedKeyRing ring = it.next(); ImportKeysListEntry item = new ImportKeysListEntry(getContext(), ring); @@ -139,7 +127,7 @@ public class ImportKeysListLoader OperationResult.OperationLog log = new OperationResult.OperationLog(); log.add(OperationResult.LogType.MSG_GET_NO_VALID_KEYS, 0); GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_NO_VALID_KEYS, log); - mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> + mEntryListWrapper = new AsyncTaskResultWrapper<> (mData, getKeyResult); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java index 80d605fb9..b8fe21941 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java @@ -33,7 +33,7 @@ public class KeyValueSpinnerAdapter extends ArrayAdapter<String> { static <K, V extends Comparable<? super V>> SortedSet<Map.Entry<K, V>> entriesSortedByValues( Map<K, V> map) { - SortedSet<Map.Entry<K, V>> sortedEntries = new TreeSet<Map.Entry<K, V>>( + SortedSet<Map.Entry<K, V>> sortedEntries = new TreeSet<>( new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java index 9f29826ef..015775669 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java @@ -21,13 +21,13 @@ import android.content.Context; import android.database.Cursor; import android.os.Parcel; import android.support.v4.util.LongSparseArray; +import android.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.CompoundButton; -import android.support.v4.widget.CursorAdapter; import android.widget.TextView; import org.sufficientlysecure.keychain.R; @@ -44,7 +44,7 @@ public class MultiUserIdsAdapter extends CursorAdapter { public MultiUserIdsAdapter(Context context, Cursor c, int flags) { super(context, c, flags); mInflater = LayoutInflater.from(context); - mCheckStates = new ArrayList<Boolean>(); + mCheckStates = new ArrayList<>(); } @Override @@ -148,7 +148,7 @@ public class MultiUserIdsAdapter extends CursorAdapter { } public ArrayList<CertifyAction> getSelectedCertifyActions() { - LongSparseArray<CertifyAction> actions = new LongSparseArray<CertifyAction>(); + LongSparseArray<CertifyAction> actions = new LongSparseArray<>(); for (int i = 0; i < mCheckStates.size(); i++) { if (mCheckStates.get(i)) { mCursor.moveToPosition(i); @@ -171,7 +171,7 @@ public class MultiUserIdsAdapter extends CursorAdapter { } } - ArrayList<CertifyAction> result = new ArrayList<CertifyAction>(actions.size()); + ArrayList<CertifyAction> result = new ArrayList<>(actions.size()); for (int i = 0; i < actions.size(); i++) { result.add(actions.valueAt(i)); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java index 330254a8f..963e77fe9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java @@ -27,7 +27,7 @@ import java.util.ArrayList; public class PagerTabStripAdapter extends FragmentPagerAdapter { protected final Activity mActivity; - protected final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); + protected final ArrayList<TabInfo> mTabs = new ArrayList<>(); static final class TabInfo { public final Class<?> clss; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index 60c130969..a836b35df 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -30,9 +30,9 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.util.Highlighter; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; /** @@ -133,11 +133,11 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { boolean enabled; if (cursor.getInt(mIndexIsRevoked) != 0) { h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, KeyFormattingUtils.STATE_REVOKED, true); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); enabled = false; } else if (cursor.getInt(mIndexIsExpiry) != 0) { h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, KeyFormattingUtils.STATE_EXPIRED, true); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray); enabled = false; } else { h.statusIcon.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index a032e96fc..ff5fbb49a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -35,11 +35,11 @@ import android.widget.ImageView; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import java.util.Calendar; import java.util.Date; @@ -160,7 +160,11 @@ public class SubkeysAdapter extends CursorAdapter { cursor.getString(INDEX_KEY_CURVE_OID) )); - if (mSaveKeyringParcel != null && mSaveKeyringParcel.mStripSubKeys.contains(keyId)) { + SubkeyChange change = mSaveKeyringParcel != null + ? mSaveKeyringParcel.getSubkeyChange(keyId) + : null; + + if (change != null && change.mDummyStrip) { algorithmStr.append(", "); final SpannableString boldStripped = new SpannableString( context.getString(R.string.key_stripped) @@ -268,12 +272,12 @@ public class SubkeysAdapter extends CursorAdapter { PorterDuff.Mode.SRC_IN); if (isRevoked) { - vStatus.setImageResource(R.drawable.status_signature_revoked_cutout); + vStatus.setImageResource(R.drawable.status_signature_revoked_cutout_24px); vStatus.setColorFilter( mContext.getResources().getColor(R.color.bg_gray), PorterDuff.Mode.SRC_IN); } else if (isExpired) { - vStatus.setImageResource(R.drawable.status_signature_expired_cutout); + vStatus.setImageResource(R.drawable.status_signature_expired_cutout_24px); vStatus.setColorFilter( mContext.getResources().getColor(R.color.bg_gray), PorterDuff.Mode.SRC_IN); @@ -297,7 +301,7 @@ public class SubkeysAdapter extends CursorAdapter { @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - View view = mInflater.inflate(R.layout.view_key_subkey_item, null); + View view = mInflater.inflate(R.layout.view_key_adv_subkey_item, null); if (mDefaultTextColor == null) { TextView keyId = (TextView) view.findViewById(R.id.subkey_item_key_id); mDefaultTextColor = keyId.getTextColors(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java index 9f5d5341e..d2359a387 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java @@ -31,8 +31,8 @@ import android.widget.TextView; import org.spongycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import java.util.Calendar; import java.util.Date; @@ -68,7 +68,7 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd public View getView(final int position, View convertView, ViewGroup parent) { if (convertView == null) { // Not recycled, inflate a new view - convertView = mInflater.inflate(R.layout.view_key_subkey_item, null); + convertView = mInflater.inflate(R.layout.view_key_adv_subkey_item, null); final ViewHolder holder = new ViewHolder(); holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id); holder.vKeyDetails = (TextView) convertView.findViewById(R.id.subkey_item_details); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java index 9ddfa90be..44afed351 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java @@ -33,7 +33,7 @@ public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar. private final Context mContext; private final ActionBar mActionBar; private final ViewPager mViewPager; - private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); + private final ArrayList<TabInfo> mTabs = new ArrayList<>(); static final class TabInfo { public final Class<?> clss; 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 a2e1930d3..ad7c9e430 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 @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; import android.database.Cursor; -import android.graphics.PorterDuff; import android.graphics.Typeface; import android.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; @@ -32,10 +31,9 @@ import android.widget.ImageView; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -45,14 +43,15 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC private LayoutInflater mInflater; private final ArrayList<Boolean> mCheckStates; private SaveKeyringParcel mSaveKeyringParcel; + private boolean mShowStatusImages; public static final String[] USER_IDS_PROJECTION = new String[]{ - UserIds._ID, - UserIds.USER_ID, - UserIds.RANK, - UserIds.VERIFIED, - UserIds.IS_PRIMARY, - UserIds.IS_REVOKED + UserPackets._ID, + UserPackets.USER_ID, + UserPackets.RANK, + UserPackets.VERIFIED, + UserPackets.IS_PRIMARY, + UserPackets.IS_REVOKED }; private static final int INDEX_ID = 0; private static final int INDEX_USER_ID = 1; @@ -62,24 +61,30 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC private static final int INDEX_IS_REVOKED = 5; public UserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes, - SaveKeyringParcel saveKeyringParcel) { + boolean showStatusImages, SaveKeyringParcel saveKeyringParcel) { super(context, c, flags); mInflater = LayoutInflater.from(context); mCheckStates = showCheckBoxes ? new ArrayList<Boolean>() : null; mSaveKeyringParcel = saveKeyringParcel; + mShowStatusImages = showStatusImages; + } + + public UserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes, + SaveKeyringParcel saveKeyringParcel) { + this(context, c, flags, showCheckBoxes, false, saveKeyringParcel); } public UserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes) { - this(context, c, flags, showCheckBoxes, null); + this(context, c, flags, showCheckBoxes, false, null); } public UserIdsAdapter(Context context, Cursor c, int flags, SaveKeyringParcel saveKeyringParcel) { - this(context, c, flags, false, saveKeyringParcel); + this(context, c, flags, false, false, saveKeyringParcel); } public UserIdsAdapter(Context context, Cursor c, int flags) { - this(context, c, flags, false, null); + this(context, c, flags, false, false, null); } @Override @@ -159,12 +164,17 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC vVerifiedLayout.setVisibility(View.GONE); } else { vEditImage.setVisibility(View.GONE); - vVerifiedLayout.setVisibility(View.VISIBLE); + + if (mShowStatusImages) { + vVerifiedLayout.setVisibility(View.VISIBLE); + } else { + vVerifiedLayout.setVisibility(View.GONE); + } } if (isRevoked) { // set revocation icon (can this even be primary?) - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_REVOKED, true); + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); // disable revoked user ids vName.setEnabled(false); @@ -186,13 +196,13 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC int isVerified = cursor.getInt(INDEX_VERIFIED); switch (isVerified) { case Certs.VERIFIED_SECRET: - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_VERIFIED, false); + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); break; case Certs.VERIFIED_SELF: - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_UNVERIFIED, false); + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); break; default: - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_INVALID, false); + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_INVALID, KeyFormattingUtils.DEFAULT_COLOR); break; } } @@ -223,7 +233,7 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC } public ArrayList<String> getSelectedUserIds() { - ArrayList<String> result = new ArrayList<String>(); + ArrayList<String> result = new ArrayList<>(); for (int i = 0; i < mCheckStates.size(); i++) { if (mCheckStates.get(i)) { mCursor.moveToPosition(i); @@ -265,7 +275,7 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - View view = mInflater.inflate(R.layout.view_key_user_id_item, null); + View view = mInflater.inflate(R.layout.view_key_adv_user_id_item, null); // only need to do this once ever, since mShowCheckBoxes is final view.findViewById(R.id.user_id_item_check_box).setVisibility(mCheckStates != null ? View.VISIBLE : View.GONE); return view; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAddedAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAddedAdapter.java index 4c0e7a492..01218a4e4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAddedAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAddedAdapter.java @@ -64,7 +64,7 @@ public class UserIdsAddedAdapter extends ArrayAdapter<String> { public View getView(final int position, View convertView, ViewGroup parent) { if (convertView == null) { // Not recycled, inflate a new view - convertView = mInflater.inflate(R.layout.view_key_user_id_item, null); + convertView = mInflater.inflate(R.layout.view_key_adv_user_id_item, null); final ViewHolder holder = new ViewHolder(); holder.vAddress = (TextView) convertView.findViewById(R.id.user_id_item_address); holder.vName = (TextView) convertView.findViewById(R.id.user_id_item_name); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java index b4119a5eb..d5376cbdc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java @@ -145,20 +145,20 @@ public class AddSubkeyDialogFragment extends DialogFragment { } { - ArrayList<Choice<Algorithm>> choices = new ArrayList<Choice<Algorithm>>(); - choices.add(new Choice<Algorithm>(Algorithm.DSA, getResources().getString( + ArrayList<Choice<Algorithm>> choices = new ArrayList<>(); + choices.add(new Choice<>(Algorithm.DSA, getResources().getString( R.string.dsa))); if (!mWillBeMasterKey) { - choices.add(new Choice<Algorithm>(Algorithm.ELGAMAL, getResources().getString( + choices.add(new Choice<>(Algorithm.ELGAMAL, getResources().getString( R.string.elgamal))); } - choices.add(new Choice<Algorithm>(Algorithm.RSA, getResources().getString( + choices.add(new Choice<>(Algorithm.RSA, getResources().getString( R.string.rsa))); - choices.add(new Choice<Algorithm>(Algorithm.ECDSA, getResources().getString( + choices.add(new Choice<>(Algorithm.ECDSA, getResources().getString( R.string.ecdsa))); - choices.add(new Choice<Algorithm>(Algorithm.ECDH, getResources().getString( + choices.add(new Choice<>(Algorithm.ECDH, getResources().getString( R.string.ecdh))); - ArrayAdapter<Choice<Algorithm>> adapter = new ArrayAdapter<Choice<Algorithm>>(context, + ArrayAdapter<Choice<Algorithm>> adapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, choices); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mAlgorithmSpinner.setAdapter(adapter); @@ -172,20 +172,20 @@ public class AddSubkeyDialogFragment extends DialogFragment { } // dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change - ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<CharSequence>(context, android.R.layout.simple_spinner_item, + ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, new ArrayList<CharSequence>(Arrays.asList(getResources().getStringArray(R.array.rsa_key_size_spinner_values)))); keySizeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mKeySizeSpinner.setAdapter(keySizeAdapter); mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length { - ArrayList<Choice<Curve>> choices = new ArrayList<Choice<Curve>>(); + ArrayList<Choice<Curve>> choices = new ArrayList<>(); - choices.add(new Choice<Curve>(Curve.NIST_P256, getResources().getString( + choices.add(new Choice<>(Curve.NIST_P256, getResources().getString( R.string.key_curve_nist_p256))); - choices.add(new Choice<Curve>(Curve.NIST_P384, getResources().getString( + choices.add(new Choice<>(Curve.NIST_P384, getResources().getString( R.string.key_curve_nist_p384))); - choices.add(new Choice<Curve>(Curve.NIST_P521, getResources().getString( + choices.add(new Choice<>(Curve.NIST_P521, getResources().getString( R.string.key_curve_nist_p521))); /* @see SaveKeyringParcel @@ -197,7 +197,7 @@ public class AddSubkeyDialogFragment extends DialogFragment { R.string.key_curve_bp_p512))); */ - ArrayAdapter<Choice<Curve>> adapter = new ArrayAdapter<Choice<Curve>>(context, + ArrayAdapter<Choice<Curve>> adapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, choices); mCurveSpinner.setAdapter(adapter); // make NIST P-256 the default 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 3eef04aa7..094c4d8a9 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 @@ -45,8 +45,8 @@ import android.widget.TextView.OnEditorActionListener; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; import java.util.regex.Matcher; @@ -85,7 +85,7 @@ public class AddUserIdDialogFragment extends DialogFragment implements OnEditorA mMessenger = getArguments().getParcelable(ARG_MESSENGER); String predefinedName = getArguments().getString(ARG_NAME); - ArrayAdapter<String> autoCompleteEmailAdapter = new ArrayAdapter<String> + ArrayAdapter<String> autoCompleteEmailAdapter = new ArrayAdapter<> (getActivity(), android.R.layout.simple_spinner_dropdown_item, ContactHelper.getPossibleUserEmails(getActivity()) ); @@ -150,7 +150,7 @@ public class AddUserIdDialogFragment extends DialogFragment implements OnEditorA mName.setThreshold(1); // Start working from first character mName.setAdapter( - new ArrayAdapter<String> + new ArrayAdapter<> (getActivity(), android.R.layout.simple_spinner_dropdown_item, ContactHelper.getPossibleUserNames(getActivity()) ) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java new file mode 100644 index 000000000..d2fa37cf7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015 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.dialog; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; + +import org.sufficientlysecure.keychain.R; + +public class AdvancedAppSettingsDialogFragment extends DialogFragment { + private static final String ARG_PACKAGE_NAME = "package_name"; + private static final String ARG_SIGNATURE = "signature"; + + /** + * Creates new instance of this fragment + */ + public static AdvancedAppSettingsDialogFragment newInstance(String packageName, String digest) { + AdvancedAppSettingsDialogFragment frag = new AdvancedAppSettingsDialogFragment(); + Bundle args = new Bundle(); + args.putString(ARG_PACKAGE_NAME, packageName); + args.putString(ARG_SIGNATURE, digest); + + frag.setArguments(args); + return frag; + } + + /** + * Creates dialog + */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final FragmentActivity activity = getActivity(); + + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); + + alert.setTitle(R.string.api_settings_advanced); + alert.setCancelable(true); + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dismiss(); + } + }); + + String packageName = getArguments().getString(ARG_PACKAGE_NAME); + String signature = getArguments().getString(ARG_SIGNATURE); + + alert.setMessage(getString(R.string.api_settings_package_name) + ": " + packageName + "\n\n" + + getString(R.string.api_settings_package_signature) + ": " + signature); + + return alert.show(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java index aa27e8391..d405b1dda 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java @@ -26,13 +26,13 @@ public class CustomAlertDialogBuilder extends AlertDialog.Builder { int dividerId = dialog.getContext().getResources().getIdentifier("android:id/titleDivider", null, null); View divider = dialog.findViewById(dividerId); if (divider != null) { - divider.setBackgroundColor(dialog.getContext().getResources().getColor(R.color.emphasis)); + divider.setBackgroundColor(dialog.getContext().getResources().getColor(R.color.header_text)); } int textViewId = dialog.getContext().getResources().getIdentifier("android:id/alertTitle", null, null); TextView tv = (TextView) dialog.findViewById(textViewId); if (tv != null) { - tv.setTextColor(dialog.getContext().getResources().getColor(R.color.emphasis)); + tv.setTextColor(dialog.getContext().getResources().getColor(R.color.header_text)); } return dialog; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java index 42e21cd57..879e3f6da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Dialog; +import android.content.ContentResolver; import android.content.DialogInterface; import android.net.Uri; import android.os.Build; @@ -30,6 +31,8 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.FileHelper; +import java.io.File; + public class DeleteFileDialogFragment extends DialogFragment { private static final String ARG_DELETE_URI = "delete_uri"; @@ -69,21 +72,38 @@ public class DeleteFileDialogFragment extends DialogFragment { @Override public void onClick(DialogInterface dialog, int id) { dismiss(); + String scheme = deleteUri.getScheme(); - // We can not securely delete Uris, so just use usual delete on them - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - if (DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri)) { + if(scheme.equals(ContentResolver.SCHEME_FILE)) { + if(new File(deleteUri.getPath()).delete()) { Toast.makeText(getActivity(), R.string.file_delete_successful, Toast.LENGTH_SHORT).show(); return; } } + else if(scheme.equals(ContentResolver.SCHEME_CONTENT)) { + // We can not securely delete Uris, so just use usual delete on them + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri)) { + Toast.makeText(getActivity(), R.string.file_delete_successful, Toast.LENGTH_SHORT).show(); + return; + } + } - if (getActivity().getContentResolver().delete(deleteUri, null, null) > 0) { - Toast.makeText(getActivity(), R.string.file_delete_successful, Toast.LENGTH_SHORT).show(); - return; + if (getActivity().getContentResolver().delete(deleteUri, null, null) > 0) { + Toast.makeText(getActivity(), R.string.file_delete_successful, Toast.LENGTH_SHORT).show(); + return; + } + + // some Uri's a ContentResolver fails to delete is handled by the java.io.File's delete + // via the path of the Uri + if(new File(deleteUri.getPath()).delete()) { + Toast.makeText(getActivity(), R.string.file_delete_successful, Toast.LENGTH_SHORT).show(); + return; + } } - Toast.makeText(getActivity(), getActivity().getString(R.string.error_file_delete_failed, deleteFilename), Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), getActivity().getString(R.string.error_file_delete_failed, + deleteFilename), Toast.LENGTH_SHORT).show(); // Note: We can't delete every file... // If possible we should find out if deletion is possible before even showing the option to do so. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java index 1cc800b2b..7ac85781f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java @@ -37,9 +37,9 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.ui.util.Notify; import java.io.File; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ActionBarHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ActionBarHelper.java deleted file mode 100644 index edd12ec73..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ActionBarHelper.java +++ /dev/null @@ -1,99 +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.util; - -import android.app.Activity; -import android.support.v7.app.ActionBar; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; - -public class ActionBarHelper { - - /** - * Sets custom view on ActionBar for Done/Cancel activities - * - * @param actionBar - * @param firstText - * @param firstDrawableId - * @param firstOnClickListener - * @param secondText - * @param secondDrawableId - * @param secondOnClickListener - */ - public static void setTwoButtonView(ActionBar actionBar, - int firstText, int firstDrawableId, OnClickListener firstOnClickListener, - int secondText, int secondDrawableId, OnClickListener secondOnClickListener) { - - // Inflate the custom action bar view - final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext() - .getSystemService(Activity.LAYOUT_INFLATER_SERVICE); - final View customActionBarView = inflater.inflate( - R.layout.actionbar_custom_view_done_cancel, null); - - TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)); - firstTextView.setText(firstText); - firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0); - customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener( - firstOnClickListener); - TextView secondTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text)); - secondTextView.setText(secondText); - secondTextView.setCompoundDrawablesWithIntrinsicBounds(secondDrawableId, 0, 0, 0); - customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener( - secondOnClickListener); - - // Show the custom action bar view and hide the normal Home icon and title. - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setDisplayShowHomeEnabled(false); - actionBar.setDisplayShowCustomEnabled(true); - actionBar.setCustomView(customActionBarView, new ActionBar.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - } - - /** - * Sets custom view on ActionBar for Done activities - * - * @param actionBar - * @param firstText - * @param firstOnClickListener - */ - public static void setOneButtonView(ActionBar actionBar, int firstText, int firstDrawableId, - OnClickListener firstOnClickListener) { - // Inflate a "Done" custom action bar view to serve as the "Up" affordance. - final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext() - .getSystemService(Activity.LAYOUT_INFLATER_SERVICE); - final View customActionBarView = inflater - .inflate(R.layout.actionbar_custom_view_done, null); - - TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)); - firstTextView.setText(firstText); - firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0); - customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener( - firstOnClickListener); - - // Show the custom action bar view and hide the normal Home icon and title. - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setDisplayShowHomeEnabled(false); - actionBar.setDisplayShowCustomEnabled(true); - actionBar.setCustomView(customActionBarView); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java index 3f84bf490..eb5c3df45 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java @@ -24,18 +24,12 @@ import android.text.style.StrikethroughSpan; public class FormattingUtils { - public static SpannableStringBuilder strikeOutText(CharSequence text) { - SpannableStringBuilder sb = new SpannableStringBuilder(text); - sb.setSpan(new StrikethroughSpan(), 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - return sb; - } - public static int dpToPx(Context context, int dp) { - return (int) ((dp * context.getResources().getDisplayMetrics().density) + 0.5); + return (int) ((dp * context.getResources().getDisplayMetrics().density) + 0.5f); } public static int pxToDp(Context context, int px) { - return (int) ((px / context.getResources().getDisplayMetrics().density) + 0.5); + return (int) ((px / context.getResources().getDisplayMetrics().density) + 0.5f); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index bff7d6b27..38ed88b9c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -377,6 +377,8 @@ public class KeyFormattingUtils { ((int) digest[2] + 256) % 256}; } + public static final int DEFAULT_COLOR = -1; + public static final int STATE_REVOKED = 1; public static final int STATE_EXPIRED = 2; public static final int STATE_VERIFIED = 3; @@ -393,20 +395,32 @@ public class KeyFormattingUtils { } public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText, int state) { - setStatusImage(context, statusIcon, statusText, state, false); + setStatusImage(context, statusIcon, statusText, state, KeyFormattingUtils.DEFAULT_COLOR, false); + } + + public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText, + int state, int color) { + setStatusImage(context, statusIcon, statusText, state, color, false); } /** * Sets status image based on constant */ public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText, - int state, boolean unobtrusive) { + int state, int color, boolean big) { switch (state) { /** GREEN: everything is good **/ case STATE_VERIFIED: { - statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_verified_cutout)); - int color = R.color.android_green_light; + if (big) { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_96px)); + } else { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_24px)); + } + if (color == KeyFormattingUtils.DEFAULT_COLOR) { + color = R.color.android_green_light; + } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); if (statusText != null) { @@ -416,8 +430,10 @@ public class KeyFormattingUtils { } case STATE_ENCRYPTED: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_lock_closed)); - int color = R.color.android_green_light; + context.getResources().getDrawable(R.drawable.status_lock_closed_24px)); + if (color == KeyFormattingUtils.DEFAULT_COLOR) { + color = R.color.android_green_light; + } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); if (statusText != null) { @@ -427,9 +443,16 @@ public class KeyFormattingUtils { } /** ORANGE: mostly bad... **/ case STATE_UNVERIFIED: { - statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout)); - int color = R.color.android_orange_light; + if (big) { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_96px)); + } else { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_24px)); + } + if (color == KeyFormattingUtils.DEFAULT_COLOR) { + color = R.color.android_orange_light; + } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); if (statusText != null) { @@ -439,8 +462,10 @@ public class KeyFormattingUtils { } case STATE_UNKNOWN_KEY: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout)); - int color = R.color.android_orange_light; + context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24px)); + if (color == KeyFormattingUtils.DEFAULT_COLOR) { + color = R.color.android_orange_light; + } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); if (statusText != null) { @@ -450,11 +475,15 @@ public class KeyFormattingUtils { } /** RED: really bad... **/ case STATE_REVOKED: { - statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout)); - int color = R.color.android_red_light; - if (unobtrusive) { - color = R.color.bg_gray; + if (big) { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_96px)); + } else { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_24px)); + } + if (color == KeyFormattingUtils.DEFAULT_COLOR) { + color = R.color.android_red_light; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -464,11 +493,15 @@ public class KeyFormattingUtils { break; } case STATE_EXPIRED: { - statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_expired_cutout)); - int color = R.color.android_red_light; - if (unobtrusive) { - color = R.color.bg_gray; + if (big) { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_96px)); + } else { + statusIcon.setImageDrawable( + context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_24px)); + } + if (color == KeyFormattingUtils.DEFAULT_COLOR) { + color = R.color.android_red_light; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -479,8 +512,10 @@ public class KeyFormattingUtils { } case STATE_NOT_ENCRYPTED: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_lock_open)); - int color = R.color.android_red_light; + context.getResources().getDrawable(R.drawable.status_lock_open_24px)); + if (color == KeyFormattingUtils.DEFAULT_COLOR) { + color = R.color.android_red_light; + } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); if (statusText != null) { @@ -490,8 +525,10 @@ public class KeyFormattingUtils { } case STATE_NOT_SIGNED: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout)); - int color = R.color.android_red_light; + context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24px)); + if (color == KeyFormattingUtils.DEFAULT_COLOR) { + color = R.color.android_red_light; + } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); if (statusText != null) { @@ -501,8 +538,10 @@ public class KeyFormattingUtils { } case STATE_INVALID: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout)); - int color = R.color.android_red_light; + context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24px)); + if (color == KeyFormattingUtils.DEFAULT_COLOR) { + color = R.color.android_red_light; + } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); if (statusText != null) { @@ -513,8 +552,10 @@ public class KeyFormattingUtils { /** special **/ case STATE_UNAVAILABLE: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout)); - int color = R.color.bg_gray; + context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24px)); + if (color == KeyFormattingUtils.DEFAULT_COLOR) { + color = R.color.bg_gray; + } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); if (statusText != null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java index 551ac039d..6f8c477c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java @@ -19,18 +19,25 @@ package org.sufficientlysecure.keychain.ui.util; import android.app.Activity; import android.content.res.Resources; +import android.graphics.Color; -import com.github.johnpersano.supertoasts.SuperCardToast; -import com.github.johnpersano.supertoasts.SuperToast; +import com.nispok.snackbar.Snackbar; +import com.nispok.snackbar.Snackbar.SnackbarDuration; +import com.nispok.snackbar.SnackbarManager; +import com.nispok.snackbar.listeners.ActionClickListener; + +import org.sufficientlysecure.keychain.R; /** - * @author danielhass * Notify wrapper which allows a more easy use of different notification libraries */ public class Notify { public static enum Style {OK, WARN, INFO, ERROR} + public static final int LENGTH_INDEFINITE = 0; + public static final int LENGTH_LONG = 3500; + /** * Shows a simple in-layout notification with the CharSequence given as parameter * @param activity @@ -39,21 +46,94 @@ public class Notify { */ 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){ + Snackbar bar = Snackbar.with(activity) + .text(text) + .duration(SnackbarDuration.LENGTH_LONG); + + switch (style) { + case OK: + break; + case WARN: + bar.textColor(activity.getResources().getColor(R.color.android_orange_light)); + break; + case ERROR: + bar.textColor(activity.getResources().getColor(R.color.android_red_light)); + break; + } + + SnackbarManager.show(bar); + + } + + public static Showable createNotify (Activity activity, int resId, int duration, Style style) { + final Snackbar bar = Snackbar.with(activity) + .text(resId); + if (duration == LENGTH_INDEFINITE) { + bar.duration(SnackbarDuration.LENGTH_INDEFINITE); + } else { + bar.duration(duration); + } + + switch (style) { + case OK: + bar.actionColor(activity.getResources().getColor(R.color.android_green_light)); + break; + case WARN: + bar.textColor(activity.getResources().getColor(R.color.android_orange_light)); + break; + case ERROR: + bar.textColor(activity.getResources().getColor(R.color.android_red_light)); + break; + } + + return new Showable () { + @Override + public void show() { + SnackbarManager.show(bar); + } + }; + } + + public static Showable createNotify(Activity activity, int resId, int duration, Style style, + final ActionListener listener, int resIdAction) { + return createNotify(activity, activity.getString(resId), duration, style, listener, resIdAction); + } + + public static Showable createNotify(Activity activity, String msg, int duration, Style style, + final ActionListener listener, int resIdAction) { + final Snackbar bar = Snackbar.with(activity) + .text(msg) + .actionLabel(resIdAction) + .actionListener(new ActionClickListener() { + @Override + public void onActionClicked(Snackbar snackbar) { + listener.onAction(); + } + }); + if (duration == LENGTH_INDEFINITE) { + bar.duration(SnackbarDuration.LENGTH_INDEFINITE); + } else { + bar.duration(duration); + } + + switch (style) { case OK: - st.setBackground(SuperToast.Background.GREEN); + bar.actionColor(activity.getResources().getColor(R.color.android_green_light)); break; case WARN: - st.setBackground(SuperToast.Background.ORANGE); + bar.textColor(activity.getResources().getColor(R.color.android_orange_light)); break; case ERROR: - st.setBackground(SuperToast.Background.RED); + bar.textColor(activity.getResources().getColor(R.color.android_red_light)); break; } - st.show(); + + return new Showable () { + @Override + public void show() { + SnackbarManager.show(bar); + } + }; } @@ -67,4 +147,15 @@ public class Notify { public static void showNotify(Activity activity, int resId, Style style) throws Resources.NotFoundException { showNotify(activity, activity.getResources().getText(resId), style); } + + public interface Showable { + public void show(); + + } + + public interface ActionListener { + public void onAction(); + + } + }
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java index 36f38045f..b8d4ea7d2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java @@ -47,7 +47,7 @@ public class QrCodeUtils { */ public static Bitmap getQRCodeBitmap(final String input, final int size) { try { - final Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>(); + final Hashtable<EncodeHintType, Object> hints = new Hashtable<>(); hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); final BitMatrix result = new QRCodeWriter().encode(input, BarcodeFormat.QR_CODE, size, size, hints); @@ -59,7 +59,7 @@ public class QrCodeUtils { for (int y = 0; y < height; y++) { final int offset = y * width; for (int x = 0; x < width; x++) { - pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE; + pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/AspectRatioImageView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/AspectRatioImageView.java new file mode 100644 index 000000000..0df5ba5e8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/AspectRatioImageView.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.ImageView; + +import org.sufficientlysecure.keychain.R; + +/** + * Maintains an aspect ratio based on either width or height. Disabled by default. + * + * from https://gist.github.com/JakeWharton/2856179 + */ +public class AspectRatioImageView extends ImageView { + // NOTE: These must be kept in sync with the AspectRatioImageView attributes in attrs.xml. + public static final int MEASUREMENT_WIDTH = 0; + public static final int MEASUREMENT_HEIGHT = 1; + + private static final float DEFAULT_ASPECT_RATIO = 1f; + private static final boolean DEFAULT_ASPECT_RATIO_ENABLED = false; + private static final int DEFAULT_DOMINANT_MEASUREMENT = MEASUREMENT_WIDTH; + + private float aspectRatio; + private boolean aspectRatioEnabled; + private int dominantMeasurement; + + public AspectRatioImageView(Context context) { + this(context, null); + } + + public AspectRatioImageView(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AspectRatioImageView); + aspectRatio = a.getFloat(R.styleable.AspectRatioImageView_aspectRatio, DEFAULT_ASPECT_RATIO); + aspectRatioEnabled = a.getBoolean(R.styleable.AspectRatioImageView_aspectRatioEnabled, + DEFAULT_ASPECT_RATIO_ENABLED); + dominantMeasurement = a.getInt(R.styleable.AspectRatioImageView_dominantMeasurement, + DEFAULT_DOMINANT_MEASUREMENT); + a.recycle(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (!aspectRatioEnabled) return; + + int newWidth; + int newHeight; + switch (dominantMeasurement) { + case MEASUREMENT_WIDTH: + newWidth = getMeasuredWidth(); + newHeight = (int) (newWidth * aspectRatio); + break; + + case MEASUREMENT_HEIGHT: + newHeight = getMeasuredHeight(); + newWidth = (int) (newHeight * aspectRatio); + break; + + default: + throw new IllegalStateException("Unknown measurement with ID " + dominantMeasurement); + } + + setMeasuredDimension(newWidth, newHeight); + } + + /** + * Get the aspect ratio for this image view. + */ + public float getAspectRatio() { + return aspectRatio; + } + + /** + * Set the aspect ratio for this image view. This will update the view instantly. + */ + public void setAspectRatio(float aspectRatio) { + this.aspectRatio = aspectRatio; + if (aspectRatioEnabled) { + requestLayout(); + } + } + + /** + * Get whether or not forcing the aspect ratio is enabled. + */ + public boolean getAspectRatioEnabled() { + return aspectRatioEnabled; + } + + /** + * set whether or not forcing the aspect ratio is enabled. This will re-layout the view. + */ + public void setAspectRatioEnabled(boolean aspectRatioEnabled) { + this.aspectRatioEnabled = aspectRatioEnabled; + requestLayout(); + } + + /** + * Get the dominant measurement for the aspect ratio. + */ + public int getDominantMeasurement() { + return dominantMeasurement; + } + + /** + * Set the dominant measurement for the aspect ratio. + * + * @see #MEASUREMENT_WIDTH + * @see #MEASUREMENT_HEIGHT + */ + public void setDominantMeasurement(int dominantMeasurement) { + if (dominantMeasurement != MEASUREMENT_HEIGHT && dominantMeasurement != MEASUREMENT_WIDTH) { + throw new IllegalArgumentException("Invalid measurement type."); + } + this.dominantMeasurement = dominantMeasurement; + requestLayout(); + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java index 14f42eb04..6d0e6556f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -27,9 +27,9 @@ import android.util.AttributeSet; import android.widget.ImageView; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; public class CertifyKeySpinner extends KeySpinner { @@ -86,32 +86,33 @@ public class CertifyKeySpinner extends KeySpinner { super.onLoadFinished(loader, data); if (loader.getId() == LOADER_ID) { + mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY); + mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED); + mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED); + // If there is only one choice, pick it by default if (mAdapter.getCount() == 2) { // preselect if key can certify - if (data.moveToPosition(1) && !data.isNull(mIndexHasCertify)) { + if (data.moveToPosition(0) && !data.isNull(mIndexHasCertify)) { setSelection(1); } } - mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY); - mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED); - mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED); } } @Override boolean setStatus(Context context, Cursor cursor, ImageView statusView) { if (cursor.getInt(mIndexIsRevoked) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_REVOKED, true); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); return false; } if (cursor.getInt(mIndexIsExpired) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_EXPIRED, true); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray); return false; } // don't invalidate the "None" entry, which is also null! if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_UNAVAILABLE, true); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_UNAVAILABLE, R.color.bg_gray); return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index e03a14989..f05f5f96b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -28,7 +28,6 @@ import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; -import android.text.SpannableStringBuilder; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -42,14 +41,13 @@ import com.tokenautocomplete.TokenCompleteTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; @@ -165,7 +163,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView { setAdapter(new EncryptKeyAdapter(Collections.<EncryptionKey>emptyList())); return; } - ArrayList<EncryptionKey> keys = new ArrayList<EncryptionKey>(); + ArrayList<EncryptionKey> keys = new ArrayList<>(); while (cursor.moveToNext()) { try { EncryptionKey key = new EncryptionKey(cursor); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java index b456b61ab..34e7b639a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java @@ -33,17 +33,16 @@ import org.sufficientlysecure.keychain.R; /** * Class representing a LinearLayout that can fold and hide it's content when pressed * To use just add the following to your xml layout - - <org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - custom:foldedLabel="@string/TEXT_TO_DISPLAY_WHEN_FOLDED" - custom:unFoldedLabel="@string/TEXT_TO_DISPLAY_WHEN_UNFOLDED"> - - <include layout="@layout/ELEMENTS_TO_BE_FOLDED"/> - - </org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout> - + * <p/> + * <org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout + * android:layout_width="wrap_content" + * android:layout_height="wrap_content" + * custom:foldedLabel="@string/TEXT_TO_DISPLAY_WHEN_FOLDED" + * custom:unFoldedLabel="@string/TEXT_TO_DISPLAY_WHEN_UNFOLDED"> + * <p/> + * <include layout="@layout/ELEMENTS_TO_BE_FOLDED"/> + * <p/> + * </org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout> */ public class FoldableLinearLayout extends LinearLayout { @@ -75,6 +74,7 @@ public class FoldableLinearLayout extends LinearLayout { /** * Load given attributes to inner variables, + * * @param context * @param attrs */ @@ -87,8 +87,8 @@ public class FoldableLinearLayout extends LinearLayout { a.recycle(); } // If any attribute isn't found then set a default one - mFoldedLabel = (mFoldedLabel == null) ? context.getString(R.id.none) : mFoldedLabel; - mUnFoldedLabel = (mUnFoldedLabel == null) ? context.getString(R.id.none) : mUnFoldedLabel; + mFoldedLabel = (mFoldedLabel == null) ? context.getString(R.string.none) : mFoldedLabel; + mUnFoldedLabel = (mUnFoldedLabel == null) ? context.getString(R.string.none) : mUnFoldedLabel; } @Override @@ -138,7 +138,7 @@ public class FoldableLinearLayout extends LinearLayout { private void initialiseInnerViews() { mFoldableIcon = (ImageView) mFoldableLayout.findViewById(R.id.foldableIcon); - mFoldableIcon.setImageResource(R.drawable.ic_action_expand); + mFoldableIcon.setImageResource(R.drawable.ic_expand_more_black_24dp); mFoldableTextView = (TextView) mFoldableLayout.findViewById(R.id.foldableText); mFoldableTextView.setText(mFoldedLabel); @@ -151,7 +151,7 @@ public class FoldableLinearLayout extends LinearLayout { public void onClick(View view) { mFolded = !mFolded; if (mFolded) { - mFoldableIcon.setImageResource(R.drawable.ic_action_collapse); + mFoldableIcon.setImageResource(R.drawable.ic_expand_less_black_24dp); mFoldableContainer.setVisibility(View.VISIBLE); AlphaAnimation animation = new AlphaAnimation(0f, 1f); animation.setDuration(mShortAnimationDuration); @@ -159,12 +159,13 @@ public class FoldableLinearLayout extends LinearLayout { mFoldableTextView.setText(mUnFoldedLabel); } else { - mFoldableIcon.setImageResource(R.drawable.ic_action_expand); + mFoldableIcon.setImageResource(R.drawable.ic_expand_more_black_24dp); AlphaAnimation animation = new AlphaAnimation(1f, 0f); animation.setDuration(mShortAnimationDuration); animation.setAnimationListener(new Animation.AnimationListener() { @Override - public void onAnimationStart(Animation animation) { } + public void onAnimationStart(Animation animation) { + } @Override public void onAnimationEnd(Animation animation) { @@ -173,7 +174,8 @@ public class FoldableLinearLayout extends LinearLayout { } @Override - public void onAnimationRepeat(Animation animation) { } + public void onAnimationRepeat(Animation animation) { + } }); mFoldableContainer.startAnimation(animation); mFoldableTextView.setText(mFoldedLabel); @@ -185,6 +187,7 @@ public class FoldableLinearLayout extends LinearLayout { /** * Adds provided child view to foldableContainer View + * * @param child */ @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index 7ec6ffe95..c8eceea50 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -24,24 +24,28 @@ import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; +import android.support.v7.internal.widget.TintSpinner; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ImageView; -import android.widget.Spinner; import android.widget.SpinnerAdapter; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; -public abstract class KeySpinner extends Spinner implements LoaderManager.LoaderCallbacks<Cursor> { +/** + * Use TintSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon. + * Related: http://stackoverflow.com/a/27713090 + */ +public abstract class KeySpinner extends TintSpinner implements LoaderManager.LoaderCallbacks<Cursor> { public interface OnKeyChangedListener { public void onKeyChanged(long masterKeyId); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ListAwareSwipeRefreshLayout.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ListAwareSwipeRefreshLayout.java index b3c3eb417..3403208d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ListAwareSwipeRefreshLayout.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ListAwareSwipeRefreshLayout.java @@ -20,13 +20,8 @@ package org.sufficientlysecure.keychain.ui.widget; import android.content.Context; import android.support.v4.widget.NoScrollableSwipeRefreshLayout; import android.util.AttributeSet; -import android.view.InputDevice; -import android.view.InputDevice.MotionRange; import android.view.MotionEvent; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.util.Log; - import se.emilsjolander.stickylistheaders.StickyListHeadersListView; public class ListAwareSwipeRefreshLayout extends NoScrollableSwipeRefreshLayout { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java index 59d05a62e..fe91e306e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java @@ -84,15 +84,15 @@ public class SignKeySpinner extends KeySpinner { @Override boolean setStatus(Context context, Cursor cursor, ImageView statusView) { if (cursor.getInt(mIndexIsRevoked) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_REVOKED, true); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); return false; } if (cursor.getInt(mIndexIsExpired) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_EXPIRED, true); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray); return false; } if (cursor.getInt(mIndexHasSign) == 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_UNAVAILABLE, true); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_UNAVAILABLE, R.color.bg_gray); return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabLayout.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabLayout.java deleted file mode 100644 index 17471c86c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabLayout.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.keychain.ui.widget; - -import android.content.Context; -import android.graphics.Typeface; -import android.os.Build; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.HorizontalScrollView; -import android.widget.TextView; - -/** - * Copied from http://developer.android.com/samples/SlidingTabsColors/index.html - */ - -/** - * To be used with ViewPager to provide a tab indicator component which give constant feedback as to - * the user's scroll progress. - * <p/> - * To use the component, simply add it to your view hierarchy. Then in your - * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call - * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for. - * <p/> - * The colors can be customized in two ways. The first and simplest is to provide an array of colors - * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The - * alternative is via the {@link TabColorizer} interface which provides you complete control over - * which color is used for any individual position. - * <p/> - * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)}, - * providing the layout ID of your custom layout. - */ -public class SlidingTabLayout extends HorizontalScrollView { - - /** - * Allows complete control over the colors drawn in the tab layout. Set with - * {@link #setCustomTabColorizer(TabColorizer)}. - */ - public interface TabColorizer { - - /** - * @return return the color of the indicator used when {@code position} is selected. - */ - int getIndicatorColor(int position); - - /** - * @return return the color of the divider drawn to the right of {@code position}. - */ - int getDividerColor(int position); - - } - - private static final int TITLE_OFFSET_DIPS = 24; - private static final int TAB_VIEW_PADDING_DIPS = 16; - private static final int TAB_VIEW_TEXT_SIZE_SP = 12; - - private int mTitleOffset; - - private int mTabViewLayoutId; - private int mTabViewTextViewId; - - private ViewPager mViewPager; - private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; - - private final SlidingTabStrip mTabStrip; - - public SlidingTabLayout(Context context) { - this(context, null); - } - - public SlidingTabLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - // Disable the Scroll Bar - setHorizontalScrollBarEnabled(false); - // Make sure that the Tab Strips fills this View - setFillViewport(true); - - mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); - - mTabStrip = new SlidingTabStrip(context); - addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - } - - /** - * Set the custom {@link TabColorizer} to be used. - * <p/> - * If you only require simple custmisation then you can use - * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve - * similar effects. - */ - public void setCustomTabColorizer(TabColorizer tabColorizer) { - mTabStrip.setCustomTabColorizer(tabColorizer); - } - - /** - * Sets the colors to be used for indicating the selected tab. These colors are treated as a - * circular array. Providing one color will mean that all tabs are indicated with the same color. - */ - public void setSelectedIndicatorColors(int... colors) { - mTabStrip.setSelectedIndicatorColors(colors); - } - - /** - * Sets the colors to be used for tab dividers. These colors are treated as a circular array. - * Providing one color will mean that all tabs are indicated with the same color. - */ - public void setDividerColors(int... colors) { - mTabStrip.setDividerColors(colors); - } - - /** - * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are - * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so - * that the layout can update it's scroll position correctly. - * - * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener) - */ - public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { - mViewPagerPageChangeListener = listener; - } - - /** - * Set the custom layout to be inflated for the tab views. - * - * @param layoutResId Layout id to be inflated - * @param textViewId id of the {@link TextView} in the inflated view - */ - public void setCustomTabView(int layoutResId, int textViewId) { - mTabViewLayoutId = layoutResId; - mTabViewTextViewId = textViewId; - } - - /** - * Sets the associated view pager. Note that the assumption here is that the pager content - * (number of tabs and tab titles) does not change after this call has been made. - */ - public void setViewPager(ViewPager viewPager) { - mTabStrip.removeAllViews(); - - mViewPager = viewPager; - if (viewPager != null) { - viewPager.setOnPageChangeListener(new InternalViewPagerListener()); - populateTabStrip(); - } - } - - /** - * Create a default view to be used for tabs. This is called if a custom tab view is not set via - * {@link #setCustomTabView(int, int)}. - */ - protected TextView createDefaultTabView(Context context) { - TextView textView = new TextView(context); - textView.setGravity(Gravity.CENTER); - textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); - textView.setTypeface(Typeface.DEFAULT_BOLD); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - // If we're running on Honeycomb or newer, then we can use the Theme's - // selectableItemBackground to ensure that the View has a pressed state - TypedValue outValue = new TypedValue(); - getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, - outValue, true); - textView.setBackgroundResource(outValue.resourceId); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style - textView.setAllCaps(true); - } - - int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density); - textView.setPadding(padding, padding, padding, padding); - - return textView; - } - - private void populateTabStrip() { - final PagerAdapter adapter = mViewPager.getAdapter(); - final View.OnClickListener tabClickListener = new TabClickListener(); - - for (int i = 0; i < adapter.getCount(); i++) { - View tabView = null; - TextView tabTitleView = null; - - if (mTabViewLayoutId != 0) { - // If there is a custom tab view layout id set, try and inflate it - tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip, - false); - tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId); - } - - if (tabView == null) { - tabView = createDefaultTabView(getContext()); - } - - if (tabTitleView == null && TextView.class.isInstance(tabView)) { - tabTitleView = (TextView) tabView; - } - - tabTitleView.setText(adapter.getPageTitle(i)); - tabView.setOnClickListener(tabClickListener); - - mTabStrip.addView(tabView); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - if (mViewPager != null) { - scrollToTab(mViewPager.getCurrentItem(), 0); - } - } - - private void scrollToTab(int tabIndex, int positionOffset) { - final int tabStripChildCount = mTabStrip.getChildCount(); - if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { - return; - } - - View selectedChild = mTabStrip.getChildAt(tabIndex); - if (selectedChild != null) { - int targetScrollX = selectedChild.getLeft() + positionOffset; - - if (tabIndex > 0 || positionOffset > 0) { - // If we're not at the first child and are mid-scroll, make sure we obey the offset - targetScrollX -= mTitleOffset; - } - - scrollTo(targetScrollX, 0); - } - } - - private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { - private int mScrollState; - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - int tabStripChildCount = mTabStrip.getChildCount(); - if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { - return; - } - - mTabStrip.onViewPagerPageChanged(position, positionOffset); - - View selectedTitle = mTabStrip.getChildAt(position); - int extraOffset = (selectedTitle != null) - ? (int) (positionOffset * selectedTitle.getWidth()) - : 0; - scrollToTab(position, extraOffset); - - if (mViewPagerPageChangeListener != null) { - mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, - positionOffsetPixels); - } - } - - @Override - public void onPageScrollStateChanged(int state) { - mScrollState = state; - - if (mViewPagerPageChangeListener != null) { - mViewPagerPageChangeListener.onPageScrollStateChanged(state); - } - } - - @Override - public void onPageSelected(int position) { - if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { - mTabStrip.onViewPagerPageChanged(position, 0f); - scrollToTab(position, 0); - } - - if (mViewPagerPageChangeListener != null) { - mViewPagerPageChangeListener.onPageSelected(position); - } - } - - } - - private class TabClickListener implements View.OnClickListener { - @Override - public void onClick(View v) { - for (int i = 0; i < mTabStrip.getChildCount(); i++) { - if (v == mTabStrip.getChildAt(i)) { - mViewPager.setCurrentItem(i); - return; - } - } - } - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabStrip.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabStrip.java deleted file mode 100644 index 4c41e12c5..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabStrip.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.keychain.ui.widget; - -import android.R; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; -import android.widget.LinearLayout; - -/** - * Copied from http://developer.android.com/samples/SlidingTabsColors/index.html - */ -class SlidingTabStrip extends LinearLayout { - - private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2; - private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26; - private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8; - private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFFAA66CC; - - private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1; - private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20; - private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f; - - private final int mBottomBorderThickness; - private final Paint mBottomBorderPaint; - - private final int mSelectedIndicatorThickness; - private final Paint mSelectedIndicatorPaint; - - private final int mDefaultBottomBorderColor; - - private final Paint mDividerPaint; - private final float mDividerHeight; - - private int mSelectedPosition; - private float mSelectionOffset; - - private SlidingTabLayout.TabColorizer mCustomTabColorizer; - private final SimpleTabColorizer mDefaultTabColorizer; - - SlidingTabStrip(Context context) { - this(context, null); - } - - SlidingTabStrip(Context context, AttributeSet attrs) { - super(context, attrs); - setWillNotDraw(false); - - final float density = getResources().getDisplayMetrics().density; - - TypedValue outValue = new TypedValue(); - context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true); - final int themeForegroundColor = outValue.data; - - mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor, - DEFAULT_BOTTOM_BORDER_COLOR_ALPHA); - - mDefaultTabColorizer = new SimpleTabColorizer(); - mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR); - mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor, - DEFAULT_DIVIDER_COLOR_ALPHA)); - - mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density); - mBottomBorderPaint = new Paint(); - mBottomBorderPaint.setColor(mDefaultBottomBorderColor); - - mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density); - mSelectedIndicatorPaint = new Paint(); - - mDividerHeight = DEFAULT_DIVIDER_HEIGHT; - mDividerPaint = new Paint(); - mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density)); - } - - void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) { - mCustomTabColorizer = customTabColorizer; - invalidate(); - } - - void setSelectedIndicatorColors(int... colors) { - // Make sure that the custom colorizer is removed - mCustomTabColorizer = null; - mDefaultTabColorizer.setIndicatorColors(colors); - invalidate(); - } - - void setDividerColors(int... colors) { - // Make sure that the custom colorizer is removed - mCustomTabColorizer = null; - mDefaultTabColorizer.setDividerColors(colors); - invalidate(); - } - - void onViewPagerPageChanged(int position, float positionOffset) { - mSelectedPosition = position; - mSelectionOffset = positionOffset; - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - final int height = getHeight(); - final int childCount = getChildCount(); - final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height); - final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null - ? mCustomTabColorizer - : mDefaultTabColorizer; - - // Thick colored underline below the current selection - if (childCount > 0) { - View selectedTitle = getChildAt(mSelectedPosition); - int left = selectedTitle.getLeft(); - int right = selectedTitle.getRight(); - int color = tabColorizer.getIndicatorColor(mSelectedPosition); - - if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { - int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1); - if (color != nextColor) { - color = blendColors(nextColor, color, mSelectionOffset); - } - - // Draw the selection partway between the tabs - View nextTitle = getChildAt(mSelectedPosition + 1); - left = (int) (mSelectionOffset * nextTitle.getLeft() + - (1.0f - mSelectionOffset) * left); - right = (int) (mSelectionOffset * nextTitle.getRight() + - (1.0f - mSelectionOffset) * right); - } - - mSelectedIndicatorPaint.setColor(color); - - canvas.drawRect(left, height - mSelectedIndicatorThickness, right, - height, mSelectedIndicatorPaint); - } - - // Thin underline along the entire bottom edge - canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint); - - // Vertical separators between the titles - int separatorTop = (height - dividerHeightPx) / 2; - for (int i = 0; i < childCount - 1; i++) { - View child = getChildAt(i); - mDividerPaint.setColor(tabColorizer.getDividerColor(i)); - canvas.drawLine(child.getRight(), separatorTop, child.getRight(), - separatorTop + dividerHeightPx, mDividerPaint); - } - } - - /** - * Set the alpha value of the {@code color} to be the given {@code alpha} value. - */ - private static int setColorAlpha(int color, byte alpha) { - return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); - } - - /** - * Blend {@code color1} and {@code color2} using the given ratio. - * - * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend, - * 0.0 will return {@code color2}. - */ - private static int blendColors(int color1, int color2, float ratio) { - final float inverseRation = 1f - ratio; - float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation); - float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation); - float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation); - return Color.rgb((int) r, (int) g, (int) b); - } - - private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer { - private int[] mIndicatorColors; - private int[] mDividerColors; - - @Override - public final int getIndicatorColor(int position) { - return mIndicatorColors[position % mIndicatorColors.length]; - } - - @Override - public final int getDividerColor(int position) { - return mDividerColors[position % mDividerColors.length]; - } - - void setIndicatorColors(int... colors) { - mIndicatorColors = colors; - } - - void setDividerColors(int... colors) { - mDividerColors = colors; - } - } -}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java index 99db634ac..c1955f75b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java @@ -23,7 +23,6 @@ import android.app.Activity; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.openpgp.PGPEncryptedData; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import java.util.HashMap; @@ -32,9 +31,9 @@ import java.util.HashMap; public class AlgorithmNames { Activity mActivity; - HashMap<Integer, String> mEncryptionNames = new HashMap<Integer, String>(); - HashMap<Integer, String> mHashNames = new HashMap<Integer, String>(); - HashMap<Integer, String> mCompressionNames = new HashMap<Integer, String>(); + HashMap<Integer, String> mEncryptionNames = new HashMap<>(); + HashMap<Integer, String> mHashNames = new HashMap<>(); + HashMap<Integer, String> mCompressionNames = new HashMap<>(); public AlgorithmNames(Activity context) { super(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index 1c4eece6b..60bc846b2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -35,8 +35,9 @@ import android.util.Patterns; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import java.io.InputStream; import java.util.ArrayList; @@ -57,10 +58,10 @@ public class ContactHelper { KeychainContract.KeyRings.EXPIRY, KeychainContract.KeyRings.IS_REVOKED}; public static final String[] USER_IDS_PROJECTION = new String[]{ - KeychainContract.UserIds.USER_ID + UserPackets.USER_ID }; - public static final String NON_REVOKED_SELECTION = KeychainContract.UserIds.IS_REVOKED + "=0"; + public static final String NON_REVOKED_SELECTION = UserPackets.IS_REVOKED + "=0"; public static final String[] ID_PROJECTION = new String[]{ContactsContract.RawContacts._ID}; public static final String[] SOURCE_ID_PROJECTION = new String[]{ContactsContract.RawContacts.SOURCE_ID}; @@ -72,20 +73,20 @@ public class ContactHelper { ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?"; public static final String ID_SELECTION = ContactsContract.RawContacts._ID + "=?"; - private static final Map<String, Bitmap> photoCache = new HashMap<String, Bitmap>(); + private static final Map<String, Bitmap> photoCache = new HashMap<>(); public static List<String> getPossibleUserEmails(Context context) { Set<String> accountMails = getAccountEmails(context); accountMails.addAll(getMainProfileContactEmails(context)); // now return the Set (without duplicates) as a List - return new ArrayList<String>(accountMails); + return new ArrayList<>(accountMails); } public static List<String> getPossibleUserNames(Context context) { Set<String> accountMails = getAccountEmails(context); Set<String> names = getContactNamesFromEmails(context, accountMails); names.addAll(getMainProfileContactName(context)); - return new ArrayList<String>(names); + return new ArrayList<>(names); } /** @@ -96,7 +97,7 @@ public class ContactHelper { */ private static Set<String> getAccountEmails(Context context) { final Account[] accounts = AccountManager.get(context).getAccounts(); - final Set<String> emailSet = new HashSet<String>(); + final Set<String> emailSet = new HashSet<>(); for (Account account : accounts) { if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) { emailSet.add(account.name); @@ -115,7 +116,7 @@ public class ContactHelper { */ private static Set<String> getContactNamesFromEmails(Context context, Set<String> emails) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - Set<String> names = new HashSet<String>(); + Set<String> names = new HashSet<>(); for (String email : emails) { ContentResolver resolver = context.getContentResolver(); Cursor profileCursor = resolver.query( @@ -127,7 +128,7 @@ public class ContactHelper { ); if (profileCursor == null) return null; - Set<String> currNames = new HashSet<String>(); + Set<String> currNames = new HashSet<>(); while (profileCursor.moveToNext()) { String name = profileCursor.getString(1); if (name != null) { @@ -139,7 +140,7 @@ public class ContactHelper { } return names; } else { - return new HashSet<String>(); + return new HashSet<>(); } } @@ -171,7 +172,7 @@ public class ContactHelper { ); if (profileCursor == null) return null; - Set<String> emails = new HashSet<String>(); + Set<String> emails = new HashSet<>(); while (profileCursor.moveToNext()) { String email = profileCursor.getString(0); if (email != null) { @@ -181,7 +182,7 @@ public class ContactHelper { profileCursor.close(); return emails; } else { - return new HashSet<String>(); + return new HashSet<>(); } } @@ -200,7 +201,7 @@ public class ContactHelper { null, null, null); if (profileCursor == null) return null; - Set<String> names = new HashSet<String>(); + Set<String> names = new HashSet<>(); // should only contain one entry! while (profileCursor.moveToNext()) { String name = profileCursor.getString(0); @@ -209,9 +210,9 @@ public class ContactHelper { } } profileCursor.close(); - return new ArrayList<String>(names); + return new ArrayList<>(names); } else { - return new ArrayList<String>(); + return new ArrayList<>(); } } @@ -220,9 +221,9 @@ public class ContactHelper { Cursor mailCursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, new String[]{ContactsContract.CommonDataKinds.Email.DATA}, null, null, null); - if (mailCursor == null) return new ArrayList<String>(); + if (mailCursor == null) return new ArrayList<>(); - Set<String> mails = new HashSet<String>(); + Set<String> mails = new HashSet<>(); while (mailCursor.moveToNext()) { String email = mailCursor.getString(0); if (email != null) { @@ -230,7 +231,7 @@ public class ContactHelper { } } mailCursor.close(); - return new ArrayList<String>(mails); + return new ArrayList<>(mails); } public static List<String> getContactNames(Context context) { @@ -238,9 +239,9 @@ public class ContactHelper { Cursor cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, new String[]{ContactsContract.Contacts.DISPLAY_NAME}, null, null, null); - if (cursor == null) return new ArrayList<String>(); + if (cursor == null) return new ArrayList<>(); - Set<String> names = new HashSet<String>(); + Set<String> names = new HashSet<>(); while (cursor.moveToNext()) { String name = cursor.getString(0); if (name != null) { @@ -248,7 +249,7 @@ public class ContactHelper { } } cursor.close(); - return new ArrayList<String>(names); + return new ArrayList<>(names); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @@ -308,7 +309,7 @@ public class ContactHelper { boolean isExpired = !cursor.isNull(4) && new Date(cursor.getLong(4) * 1000).before(new Date()); boolean isRevoked = cursor.getInt(5) > 0; int rawContactId = findRawContactId(resolver, fingerprint); - ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); + ArrayList<ContentProviderOperation> ops = new ArrayList<>(); // Do not store expired or revoked keys in contact db - and remove them if they already exist if (isExpired || isRevoked) { @@ -350,7 +351,7 @@ public class ContactHelper { * @return a set of all key fingerprints currently present in the contact db */ private static Set<String> getRawContactFingerprints(ContentResolver resolver) { - HashSet<String> result = new HashSet<String>(); + HashSet<String> result = new HashSet<>(); Cursor fingerprints = resolver.query(ContactsContract.RawContacts.CONTENT_URI, SOURCE_ID_PROJECTION, ACCOUNT_TYPE_SELECTION, new String[]{Constants.ACCOUNT_TYPE}, null); if (fingerprints != null) { @@ -413,7 +414,7 @@ public class ContactHelper { int rawContactId, long masterKeyId) { ops.add(selectByRawContactAndItemType(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI), rawContactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).build()); - Cursor ids = resolver.query(KeychainContract.UserIds.buildUserIdsUri(masterKeyId), + Cursor ids = resolver.query(UserPackets.buildUserIdsUri(masterKeyId), USER_IDS_PROJECTION, NON_REVOKED_SELECTION, null, null); if (ids != null) { while (ids.moveToNext()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java index 49d4d8bf8..8334b37ec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java @@ -42,13 +42,13 @@ public class EmailKeyHelper { public static void importAll(Context context, Messenger messenger, List<String> mails) { // Collect all candidates as ImportKeysListEntry (set for deduplication) - Set<ImportKeysListEntry> entries = new HashSet<ImportKeysListEntry>(); + Set<ImportKeysListEntry> entries = new HashSet<>(); for (String mail : mails) { entries.addAll(getEmailKeys(context, mail)); } // Put them in a list and import - ArrayList<ParcelableKeyRing> keys = new ArrayList<ParcelableKeyRing>(entries.size()); + ArrayList<ParcelableKeyRing> keys = new ArrayList<>(entries.size()); for (ImportKeysListEntry entry : entries) { keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(), null)); } @@ -56,7 +56,7 @@ public class EmailKeyHelper { } public static Set<ImportKeysListEntry> getEmailKeys(Context context, String mail) { - Set<ImportKeysListEntry> keys = new HashSet<ImportKeysListEntry>(); + Set<ImportKeysListEntry> keys = new HashSet<>(); // Try _hkp._tcp SRV record first String[] mailparts = mail.split("@"); @@ -90,7 +90,7 @@ public class EmailKeyHelper { } public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer) { - Set<ImportKeysListEntry> keys = new HashSet<ImportKeysListEntry>(); + Set<ImportKeysListEntry> keys = new HashSet<>(); try { for (ImportKeysListEntry key : keyServer.search(mail)) { if (key.isRevoked() || key.isExpired()) continue; @@ -103,6 +103,6 @@ public class EmailKeyHelper { } catch (Keyserver.QueryFailedException ignored) { } catch (Keyserver.QueryNeedsRepairException ignored) { } - return new ArrayList<ImportKeysListEntry>(keys); + return new ArrayList<>(keys); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java index 7492d95b2..1d78ed80e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java @@ -24,13 +24,11 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; -import android.support.v7.app.ActionBarActivity; -import android.widget.Toast; +import android.support.v4.app.FragmentActivity; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.ExportResult; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; @@ -42,9 +40,9 @@ import java.io.File; public class ExportHelper { protected File mExportFile; - ActionBarActivity mActivity; + FragmentActivity mActivity; - public ExportHelper(ActionBarActivity activity) { + public ExportHelper(FragmentActivity activity) { super(); this.mActivity = activity; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java index f89ffd139..1f73dcb28 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java @@ -25,16 +25,28 @@ import java.io.InputStream; public class InputData { private PositionAwareInputStream mInputStream; private long mSize; + String mOriginalFilename; + + public InputData(InputStream inputStream, long size, String originalFilename) { + mInputStream = new PositionAwareInputStream(inputStream); + mSize = size; + mOriginalFilename = originalFilename; + } public InputData(InputStream inputStream, long size) { mInputStream = new PositionAwareInputStream(inputStream); mSize = size; + mOriginalFilename = ""; } public InputStream getInputStream() { return mInputStream; } + public String getOriginalFilename () { + return mOriginalFilename; + } + public long getSize() { return mSize; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Iso7816TLV.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Iso7816TLV.java index 90afd3bc0..c0483ad04 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Iso7816TLV.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Iso7816TLV.java @@ -125,7 +125,7 @@ public class Iso7816TLV { public static Iso7816TLV[] readList(byte[] data, boolean recursive) throws IOException { ByteBuffer buf = ByteBuffer.wrap(data); - ArrayList<Iso7816TLV> result = new ArrayList<Iso7816TLV>(); + ArrayList<Iso7816TLV> result = new ArrayList<>(); // read while data is available. this will fail if there is trailing data! while (buf.hasRemaining()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java index 76ec9f75f..943b913d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java @@ -17,21 +17,6 @@ package org.sufficientlysecure.keychain.util; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Messenger; - -import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; - -import java.util.ArrayList; -import java.util.List; - public class KeyUpdateHelper { /* diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java index b4f7c5767..8b165cd57 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java @@ -21,9 +21,6 @@ import android.os.Bundle; import org.sufficientlysecure.keychain.Constants; -import java.io.IOException; -import java.io.StreamTokenizer; -import java.io.StringReader; import java.util.Iterator; import java.util.Set; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java index 3081021cf..6f9cb277e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java @@ -32,9 +32,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.util.ArrayList; import java.util.Iterator; -import java.util.List; /** * When sending large data (over 1MB) through Androids Binder IPC you get @@ -106,7 +104,6 @@ public class ParcelableFileCache<E extends Parcelable> { throw new IOException(e); } - // yes this is sloppy data flow. WE WOULDN'T NEED THIS WITH TUPLE RETURN TYPES final int numEntries = ois.readInt(); return new IteratorWithSize<E>() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index 65f4af880..a36af5c87 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -172,7 +172,7 @@ public class Preferences { } public boolean useDefaultYubikeyPin() { - return mSharedPreferences.getBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, true); + return mSharedPreferences.getBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, false); } public void setUseDefaultYubikeyPin(boolean useDefaultYubikeyPin) { @@ -182,7 +182,7 @@ public class Preferences { } public boolean useNumKeypadForYubikeyPin() { - return mSharedPreferences.getBoolean(Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN, false); + return mSharedPreferences.getBoolean(Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN, true); } public void setUseNumKeypadForYubikeyPin(boolean useNumKeypadForYubikeyPin) { @@ -200,7 +200,7 @@ public class Preferences { public String[] getKeyServers() { String rawData = mSharedPreferences.getString(Constants.Pref.KEY_SERVERS, Constants.Defaults.KEY_SERVERS); - Vector<String> servers = new Vector<String>(); + Vector<String> servers = new Vector<>(); String chunks[] = rawData.split(","); for (String c : chunks) { String tmp = c.trim(); @@ -281,7 +281,7 @@ public class Preferences { case 3: { // migrate keyserver to hkps String[] serversArray = getKeyServers(); - ArrayList<String> servers = new ArrayList<String>(Arrays.asList(serversArray)); + ArrayList<String> servers = new ArrayList<>(Arrays.asList(serversArray)); ListIterator<String> it = servers.listIterator(); while (it.hasNext()) { String server = it.next(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java index 51e58565f..120b84a3b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java @@ -43,16 +43,18 @@ public class ShareHelper { * Put together from some stackoverflow posts... */ public Intent createChooserExcluding(Intent prototype, String title, String[] activityBlacklist) { - // Produced an empty list on Huawei U8860 with Android Version 4.0.3 and weird results on 2.3 + // Produced an empty list on Huawei U8860 with Android Version 4.0.3 // TODO: test on 4.1, 4.2, 4.3, only tested on 4.4 - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + // Disabled on 5.0 because using EXTRA_INITIAL_INTENTS prevents the usage based sorting + // introduced in 5.0: https://medium.com/@xXxXxXxXxXam/how-lollipops-share-menu-is-organized-d204888f606d + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return Intent.createChooser(prototype, title); } - List<LabeledIntent> targetedShareIntents = new ArrayList<LabeledIntent>(); + List<LabeledIntent> targetedShareIntents = new ArrayList<>(); List<ResolveInfo> resInfoList = mContext.getPackageManager().queryIntentActivities(prototype, 0); - List<ResolveInfo> resInfoListFiltered = new ArrayList<ResolveInfo>(); + List<ResolveInfo> resInfoListFiltered = new ArrayList<>(); if (!resInfoList.isEmpty()) { for (ResolveInfo resolveInfo : resInfoList) { // do not add blacklisted ones diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java index 9946d81aa..4ff14e3bb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java @@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.util; import android.content.res.AssetManager; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -50,7 +49,7 @@ public class TlsHelper { } } - private static Map<String, byte[]> sStaticCA = new HashMap<String, byte[]>(); + private static Map<String, byte[]> sStaticCA = new HashMap<>(); public static void addStaticCA(String domain, byte[] certificate) { sStaticCA.put(domain, certificate); @@ -120,13 +119,7 @@ public class TlsHelper { urlConnection.setSSLSocketFactory(context.getSocketFactory()); return urlConnection; - } catch (CertificateException e) { - throw new TlsHelperException(e); - } catch (NoSuchAlgorithmException e) { - throw new TlsHelperException(e); - } catch (KeyStoreException e) { - throw new TlsHelperException(e); - } catch (KeyManagementException e) { + } catch (CertificateException | KeyManagementException | KeyStoreException | NoSuchAlgorithmException e) { throw new TlsHelperException(e); } } |