From f5efd73d33e80657b1d4af300f0916ea756e5753 Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Fri, 16 Jul 2010 20:13:12 +0000 Subject: set svn:eol-style to LF for all code/resource files --- src/org/thialfihar/android/apg/Apg.java | 3794 ++++++++++---------- .../android/apg/AskForSecretKeyPassPhrase.java | 224 +- src/org/thialfihar/android/apg/BaseActivity.java | 762 ++-- .../thialfihar/android/apg/CachedPassPhrase.java | 90 +- src/org/thialfihar/android/apg/Constants.java | 70 +- .../thialfihar/android/apg/DataDestination.java | 158 +- src/org/thialfihar/android/apg/DataSource.java | 176 +- src/org/thialfihar/android/apg/FileDialog.java | 236 +- .../thialfihar/android/apg/GeneralActivity.java | 354 +- src/org/thialfihar/android/apg/Id.java | 310 +- src/org/thialfihar/android/apg/InputData.java | 50 +- .../android/apg/IntegerListPreference.java | 190 +- .../thialfihar/android/apg/KeyListActivity.java | 1524 ++++---- .../thialfihar/android/apg/MailListActivity.java | 436 +-- .../android/apg/PositionAwareInputStream.java | 134 +- .../android/apg/PreferencesActivity.java | 360 +- src/org/thialfihar/android/apg/Primes.java | 370 +- .../android/apg/ProgressDialogUpdater.java | 46 +- .../android/apg/PublicKeyListActivity.java | 124 +- .../android/apg/SecretKeyListActivity.java | 360 +- .../android/apg/SelectPublicKeyListAdapter.java | 450 +-- .../android/apg/SelectSecretKeyListAdapter.java | 348 +- src/org/thialfihar/android/apg/Service.java | 156 +- .../thialfihar/android/apg/provider/Accounts.java | 54 +- .../thialfihar/android/apg/provider/Database.java | 1210 +++---- .../thialfihar/android/apg/provider/KeyRings.java | 66 +- .../thialfihar/android/apg/provider/UserIds.java | 62 +- .../thialfihar/android/apg/ui/widget/Editor.java | 50 +- .../android/apg/ui/widget/KeyEditor.java | 484 +-- .../android/apg/ui/widget/SectionView.java | 674 ++-- .../android/apg/ui/widget/UserIdEditor.java | 388 +- src/org/thialfihar/android/apg/utils/Choice.java | 88 +- .../android/apg/utils/IterableIterator.java | 62 +- 33 files changed, 6930 insertions(+), 6930 deletions(-) (limited to 'src') diff --git a/src/org/thialfihar/android/apg/Apg.java b/src/org/thialfihar/android/apg/Apg.java index e2c6df5b9..58639cee0 100644 --- a/src/org/thialfihar/android/apg/Apg.java +++ b/src/org/thialfihar/android/apg/Apg.java @@ -1,1897 +1,1897 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.EOFException; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.math.BigInteger; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SecureRandom; -import java.security.Security; -import java.security.SignatureException; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Vector; -import java.util.regex.Pattern; - -import org.bouncycastle2.bcpg.ArmoredInputStream; -import org.bouncycastle2.bcpg.ArmoredOutputStream; -import org.bouncycastle2.bcpg.BCPGOutputStream; -import org.bouncycastle2.bcpg.CompressionAlgorithmTags; -import org.bouncycastle2.bcpg.HashAlgorithmTags; -import org.bouncycastle2.bcpg.SymmetricKeyAlgorithmTags; -import org.bouncycastle2.bcpg.sig.KeyFlags; -import org.bouncycastle2.jce.provider.BouncyCastleProvider; -import org.bouncycastle2.jce.spec.ElGamalParameterSpec; -import org.bouncycastle2.openpgp.PGPCompressedData; -import org.bouncycastle2.openpgp.PGPCompressedDataGenerator; -import org.bouncycastle2.openpgp.PGPEncryptedData; -import org.bouncycastle2.openpgp.PGPEncryptedDataGenerator; -import org.bouncycastle2.openpgp.PGPEncryptedDataList; -import org.bouncycastle2.openpgp.PGPException; -import org.bouncycastle2.openpgp.PGPKeyPair; -import org.bouncycastle2.openpgp.PGPKeyRingGenerator; -import org.bouncycastle2.openpgp.PGPLiteralData; -import org.bouncycastle2.openpgp.PGPLiteralDataGenerator; -import org.bouncycastle2.openpgp.PGPObjectFactory; -import org.bouncycastle2.openpgp.PGPOnePassSignature; -import org.bouncycastle2.openpgp.PGPOnePassSignatureList; -import org.bouncycastle2.openpgp.PGPPBEEncryptedData; -import org.bouncycastle2.openpgp.PGPPrivateKey; -import org.bouncycastle2.openpgp.PGPPublicKey; -import org.bouncycastle2.openpgp.PGPPublicKeyEncryptedData; -import org.bouncycastle2.openpgp.PGPPublicKeyRing; -import org.bouncycastle2.openpgp.PGPSecretKey; -import org.bouncycastle2.openpgp.PGPSecretKeyRing; -import org.bouncycastle2.openpgp.PGPSignature; -import org.bouncycastle2.openpgp.PGPSignatureGenerator; -import org.bouncycastle2.openpgp.PGPSignatureList; -import org.bouncycastle2.openpgp.PGPSignatureSubpacketGenerator; -import org.bouncycastle2.openpgp.PGPSignatureSubpacketVector; -import org.bouncycastle2.openpgp.PGPUtil; -import org.thialfihar.android.apg.provider.DataProvider; -import org.thialfihar.android.apg.provider.Database; -import org.thialfihar.android.apg.provider.KeyRings; -import org.thialfihar.android.apg.provider.Keys; -import org.thialfihar.android.apg.provider.UserIds; -import org.thialfihar.android.apg.ui.widget.KeyEditor; -import org.thialfihar.android.apg.ui.widget.SectionView; -import org.thialfihar.android.apg.ui.widget.UserIdEditor; -import org.thialfihar.android.apg.utils.IterableIterator; - -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.view.ViewGroup; - -public class Apg { - private static final String mApgPackageName = "org.thialfihar.android.apg"; - - public static class Intent { - public static final String DECRYPT = "org.thialfihar.android.apg.intent.DECRYPT"; - public static final String ENCRYPT = "org.thialfihar.android.apg.intent.ENCRYPT"; - public static final String DECRYPT_FILE = "org.thialfihar.android.apg.intent.DECRYPT_FILE"; - public static final String ENCRYPT_FILE = "org.thialfihar.android.apg.intent.ENCRYPT_FILE"; - public static final String DECRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.DECRYPT_AND_RETURN"; - public static final String ENCRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.ENCRYPT_AND_RETURN"; - public static final String SELECT_PUBLIC_KEYS = "org.thialfihar.android.apg.intent.SELECT_PUBLIC_KEYS"; - public static final String SELECT_SECRET_KEY = "org.thialfihar.android.apg.intent.SELECT_SECRET_KEY"; - public static final String IMPORT = "org.thialfihar.android.apg.intent.IMPORT"; - } - - public static final String EXTRA_TEXT = "text"; - public static final String EXTRA_DATA = "data"; - public static final String EXTRA_STATUS = "status"; - public static final String EXTRA_ERROR = "error"; - public static final String EXTRA_DECRYPTED_MESSAGE = "decryptedMessage"; - public static final String EXTRA_DECRYPTED_DATA = "decryptedData"; - public static final String EXTRA_ENCRYPTED_MESSAGE = "encryptedMessage"; - public static final String EXTRA_ENCRYPTED_DATA = "encryptedData"; - public static final String EXTRA_RESULT_URI = "resultUri"; - public static final String EXTRA_SIGNATURE = "signature"; - public static final String EXTRA_SIGNATURE_KEY_ID = "signatureKeyId"; - public static final String EXTRA_SIGNATURE_USER_ID = "signatureUserId"; - public static final String EXTRA_SIGNATURE_SUCCESS = "signatureSuccess"; - public static final String EXTRA_SIGNATURE_UNKNOWN = "signatureUnknown"; - public static final String EXTRA_USER_ID = "userId"; - public static final String EXTRA_KEY_ID = "keyId"; - public static final String EXTRA_REPLY_TO = "replyTo"; - public static final String EXTRA_SEND_TO = "sendTo"; - public static final String EXTRA_SUBJECT = "subject"; - public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryptionKeyIds"; - public static final String EXTRA_SELECTION = "selection"; - public static final String EXTRA_MESSAGE = "message"; - public static final String EXTRA_PROGRESS = "progress"; - public static final String EXTRA_MAX = "max"; - public static final String EXTRA_ACCOUNT = "account"; - public static final String EXTRA_ASCII_ARMOUR = "asciiArmour"; - public static final String EXTRA_BINARY = "binary"; - - public static final String AUTHORITY = DataProvider.AUTHORITY; - - public static final Uri CONTENT_URI_SECRET_KEY_RINGS = - Uri.parse("content://" + AUTHORITY + "/key_rings/secret/"); - public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID = - Uri.parse("content://" + AUTHORITY + "/key_rings/secret/key_id/"); - public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_EMAILS = - Uri.parse("content://" + AUTHORITY + "/key_rings/secret/emails/"); - - public static final Uri CONTENT_URI_PUBLIC_KEY_RINGS = - Uri.parse("content://" + AUTHORITY + "/key_rings/public/"); - public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_KEY_ID = - Uri.parse("content://" + AUTHORITY + "/key_rings/public/key_id/"); - public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS = - Uri.parse("content://" + AUTHORITY + "/key_rings/public/emails/"); - - private static String VERSION = null; - - private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = - new int[] { - SymmetricKeyAlgorithmTags.AES_256, - SymmetricKeyAlgorithmTags.AES_192, - SymmetricKeyAlgorithmTags.AES_128, - SymmetricKeyAlgorithmTags.CAST5, - SymmetricKeyAlgorithmTags.TRIPLE_DES }; - private static final int[] PREFERRED_HASH_ALGORITHMS = - new int[] { - HashAlgorithmTags.SHA1, - HashAlgorithmTags.SHA256, - HashAlgorithmTags.RIPEMD160 }; - private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = - new int[] { - CompressionAlgorithmTags.ZLIB, - CompressionAlgorithmTags.BZIP2, - CompressionAlgorithmTags.ZIP }; - - public static Pattern PGP_MESSAGE = - Pattern.compile(".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", - Pattern.DOTALL); - - public static Pattern PGP_SIGNED_MESSAGE = - Pattern.compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", - Pattern.DOTALL); - - private static HashMap mPassPhraseCache = - new HashMap(); - private static String mEditPassPhrase = null; - - private static Database mDatabase = null; - - public static class GeneralException extends Exception { - static final long serialVersionUID = 0xf812773342L; - - public GeneralException(String message) { - super(message); - } - } - - public static class NoAsymmetricEncryptionException extends Exception { - static final long serialVersionUID = 0xf812773343L; - - public NoAsymmetricEncryptionException() { - super(); - } - } - - public static void initialize(Context context) { - if (mDatabase == null) { - mDatabase = new Database(context); - } - } - - public static Database getDatabase() { - return mDatabase; - } - - public static void setEditPassPhrase(String passPhrase) { - mEditPassPhrase = passPhrase; - } - - public static String getEditPassPhrase() { - return mEditPassPhrase; - } - - public static void setCachedPassPhrase(long keyId, String passPhrase) { - mPassPhraseCache.put(keyId, new CachedPassPhrase(new Date().getTime(), passPhrase)); - } - - public static String getCachedPassPhrase(long keyId) { - long realId = keyId; - if (realId != Id.key.symmetric) { - PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); - if (keyRing == null) { - return null; - } - PGPSecretKey masterKey = getMasterKey(keyRing); - if (masterKey == null) { - return null; - } - realId = masterKey.getKeyID(); - } - CachedPassPhrase cpp = mPassPhraseCache.get(realId); - if (cpp == null) { - return null; - } - // set it again to reset the cache life cycle - setCachedPassPhrase(realId, cpp.passPhrase); - return cpp.passPhrase; - } - - public static int cleanUpCache(int ttl, int initialDelay) { - int delay = initialDelay; - long realTtl = ttl * 1000; - long now = new Date().getTime(); - Vector oldKeys = new Vector(); - for (Map.Entry pair : mPassPhraseCache.entrySet()) { - long lived = now - pair.getValue().timestamp; - if (lived >= realTtl) { - oldKeys.add(pair.getKey()); - } else { - // see, whether the remaining time for this cache entry improves our - // check delay - long nextCheck = realTtl - lived + 1000; - if (nextCheck < delay) { - delay = (int)nextCheck; - } - } - } - - for (long keyId : oldKeys) { - mPassPhraseCache.remove(keyId); - } - - return delay; - } - - public static PGPSecretKey createKey(Context context, - int algorithmChoice, int keySize, String passPhrase, - PGPSecretKey masterKey) - throws NoSuchAlgorithmException, PGPException, NoSuchProviderException, - GeneralException, InvalidAlgorithmParameterException { - - if (keySize < 512) { - throw new GeneralException(context.getString(R.string.error_keySizeMinimum512bit)); - } - - Security.addProvider(new BouncyCastleProvider()); - - if (passPhrase == null) { - passPhrase = ""; - } - - int algorithm = 0; - KeyPairGenerator keyGen = null; - - switch (algorithmChoice) { - case Id.choice.algorithm.dsa: { - keyGen = KeyPairGenerator.getInstance("DSA", new BouncyCastleProvider()); - keyGen.initialize(keySize, new SecureRandom()); - algorithm = PGPPublicKey.DSA; - break; - } - - case Id.choice.algorithm.elgamal: { - if (masterKey == null) { - throw new GeneralException(context.getString(R.string.error_masterKeyMustNotBeElGamal)); - } - keyGen = KeyPairGenerator.getInstance("ELGAMAL", new BouncyCastleProvider()); - BigInteger p = Primes.getBestPrime(keySize); - BigInteger g = new BigInteger("2"); - - ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g); - - keyGen.initialize(elParams); - algorithm = PGPPublicKey.ELGAMAL_ENCRYPT; - break; - } - - case Id.choice.algorithm.rsa: { - keyGen = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider()); - keyGen.initialize(keySize, new SecureRandom()); - - algorithm = PGPPublicKey.RSA_GENERAL; - break; - } - - default: { - throw new GeneralException(context.getString(R.string.error_unknownAlgorithmChoice)); - } - } - - PGPKeyPair keyPair = new PGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date()); - - PGPSecretKey secretKey = null; - if (masterKey == null) { - // enough for now, as we assemble the key again later anyway - secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, keyPair, "", - PGPEncryptedData.CAST5, passPhrase.toCharArray(), - null, null, - new SecureRandom(), new BouncyCastleProvider().getName()); - - } else { - PGPPublicKey tmpKey = masterKey.getPublicKey(); - PGPPublicKey masterPublicKey = - new PGPPublicKey(tmpKey.getAlgorithm(), - tmpKey.getKey(new BouncyCastleProvider()), - tmpKey.getCreationTime()); - PGPPrivateKey masterPrivateKey = - masterKey.extractPrivateKey(passPhrase.toCharArray(), - new BouncyCastleProvider()); - - PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); - PGPKeyRingGenerator ringGen = - new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, - masterKeyPair, "", - PGPEncryptedData.CAST5, passPhrase.toCharArray(), - null, null, - new SecureRandom(), new BouncyCastleProvider().getName()); - ringGen.addSubKey(keyPair); - PGPSecretKeyRing secKeyRing = ringGen.generateSecretKeyRing(); - Iterator it = secKeyRing.getSecretKeys(); - // first one is the master key - it.next(); - secretKey = (PGPSecretKey) it.next(); - } - - return secretKey; - } - - private static long getNumDaysBetween(GregorianCalendar first, GregorianCalendar second) { - GregorianCalendar tmp = new GregorianCalendar(); - tmp.setTime(first.getTime()); - long numDays = (second.getTimeInMillis() - first.getTimeInMillis()) / 1000 / 86400; - tmp.add(Calendar.DAY_OF_MONTH, (int)numDays); - while (tmp.before(second)) { - tmp.add(Calendar.DAY_OF_MONTH, 1); - ++numDays; - } - return numDays; - } - - public static void buildSecretKey(Activity context, - SectionView userIdsView, SectionView keysView, - String oldPassPhrase, String newPassPhrase, - ProgressDialogUpdater progress) - throws Apg.GeneralException, NoSuchProviderException, PGPException, - NoSuchAlgorithmException, SignatureException, IOException, Database.GeneralException { - - progress.setProgress(R.string.progress_buildingKey, 0, 100); - - Security.addProvider(new BouncyCastleProvider()); - - if (oldPassPhrase == null || oldPassPhrase.equals("")) { - oldPassPhrase = ""; - } - - if (newPassPhrase == null || newPassPhrase.equals("")) { - newPassPhrase = ""; - } - - Vector userIds = new Vector(); - Vector keys = new Vector(); - - ViewGroup userIdEditors = userIdsView.getEditors(); - ViewGroup keyEditors = keysView.getEditors(); - - boolean gotMainUserId = false; - for (int i = 0; i < userIdEditors.getChildCount(); ++i) { - UserIdEditor editor = (UserIdEditor)userIdEditors.getChildAt(i); - String userId = null; - try { - userId = editor.getValue(); - } catch (UserIdEditor.NoNameException e) { - throw new Apg.GeneralException(context.getString(R.string.error_userIdNeedsAName)); - } catch (UserIdEditor.NoEmailException e) { - throw new Apg.GeneralException(context.getString(R.string.error_userIdNeedsAnEmailAddress)); - } catch (UserIdEditor.InvalidEmailException e) { - throw new Apg.GeneralException("" + e); - } - - if (userId.equals("")) { - continue; - } - - if (editor.isMainUserId()) { - userIds.insertElementAt(userId, 0); - gotMainUserId = true; - } else { - userIds.add(userId); - } - } - - if (userIds.size() == 0) { - throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsAUserId)); - } - - if (!gotMainUserId) { - throw new Apg.GeneralException(context.getString(R.string.error_mainUserIdMustNotBeEmpty)); - } - - if (keyEditors.getChildCount() == 0) { - throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsMasterKey)); - } - - for (int i = 0; i < keyEditors.getChildCount(); ++i) { - KeyEditor editor = (KeyEditor)keyEditors.getChildAt(i); - keys.add(editor.getValue()); - } - - progress.setProgress(R.string.progress_preparingMasterKey, 10, 100); - KeyEditor keyEditor = (KeyEditor) keyEditors.getChildAt(0); - int usageId = keyEditor.getUsage(); - boolean canSign = (usageId == Id.choice.usage.sign_only || - usageId == Id.choice.usage.sign_and_encrypt); - boolean canEncrypt = (usageId == Id.choice.usage.encrypt_only || - usageId == Id.choice.usage.sign_and_encrypt); - - String mainUserId = userIds.get(0); - - PGPSecretKey masterKey = keys.get(0); - PGPPublicKey tmpKey = masterKey.getPublicKey(); - PGPPublicKey masterPublicKey = - new PGPPublicKey(tmpKey.getAlgorithm(), - tmpKey.getKey(new BouncyCastleProvider()), - tmpKey.getCreationTime()); - PGPPrivateKey masterPrivateKey = - masterKey.extractPrivateKey(oldPassPhrase.toCharArray(), - new BouncyCastleProvider()); - - progress.setProgress(R.string.progress_certifyingMasterKey, 20, 100); - for (int i = 0; i < userIds.size(); ++i) { - String userId = userIds.get(i); - - PGPSignatureGenerator sGen = - new PGPSignatureGenerator(masterPublicKey.getAlgorithm(), - HashAlgorithmTags.SHA1, new BouncyCastleProvider()); - - sGen.initSign(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); - - PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); - - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification); - } - - // TODO: cross-certify the master key with every sub key - - PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); - - PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); - - int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA; - if (canEncrypt) { - keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; - } - hashedPacketsGen.setKeyFlags(true, keyFlags); - - hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); - hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); - hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); - - // TODO: this doesn't work quite right yet - if (keyEditor.getExpiryDate() != null) { - GregorianCalendar creationDate = new GregorianCalendar(); - creationDate.setTime(getCreationDate(masterKey)); - GregorianCalendar expiryDate = keyEditor.getExpiryDate(); - long numDays = getNumDaysBetween(creationDate, expiryDate); - if (numDays <= 0) { - throw new GeneralException(context.getString(R.string.error_expiryMustComeAfterCreation)); - } - hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400); - } - - progress.setProgress(R.string.progress_buildingMasterKeyRing, 30, 100); - PGPKeyRingGenerator keyGen = - new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, - masterKeyPair, mainUserId, - PGPEncryptedData.CAST5, newPassPhrase.toCharArray(), - hashedPacketsGen.generate(), unhashedPacketsGen.generate(), - new SecureRandom(), new BouncyCastleProvider().getName()); - - progress.setProgress(R.string.progress_addingSubKeys, 40, 100); - for (int i = 1; i < keys.size(); ++i) { - progress.setProgress(40 + 50 * (i - 1)/ (keys.size() - 1), 100); - PGPSecretKey subKey = keys.get(i); - keyEditor = (KeyEditor) keyEditors.getChildAt(i); - PGPPublicKey subPublicKey = subKey.getPublicKey(); - PGPPrivateKey subPrivateKey = - subKey.extractPrivateKey(oldPassPhrase.toCharArray(), - new BouncyCastleProvider()); - PGPKeyPair subKeyPair = - new PGPKeyPair(subPublicKey.getAlgorithm(), - subPublicKey.getKey(new BouncyCastleProvider()), - subPrivateKey.getKey(), - subPublicKey.getCreationTime()); - - hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); - - keyFlags = 0; - usageId = keyEditor.getUsage(); - canSign = (usageId == Id.choice.usage.sign_only || - usageId == Id.choice.usage.sign_and_encrypt); - canEncrypt = (usageId == Id.choice.usage.encrypt_only || - usageId == Id.choice.usage.sign_and_encrypt); - if (canSign) { - keyFlags |= KeyFlags.SIGN_DATA; - } - if (canEncrypt) { - keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; - } - hashedPacketsGen.setKeyFlags(true, keyFlags); - - // TODO: this doesn't work quite right yet - if (keyEditor.getExpiryDate() != null) { - GregorianCalendar creationDate = new GregorianCalendar(); - creationDate.setTime(getCreationDate(masterKey)); - GregorianCalendar expiryDate = keyEditor.getExpiryDate(); - long numDays = getNumDaysBetween(creationDate, expiryDate); - if (numDays <= 0) { - throw new GeneralException(context.getString(R.string.error_expiryMustComeAfterCreation)); - } - hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400); - } - - keyGen.addSubKey(subKeyPair, - hashedPacketsGen.generate(), unhashedPacketsGen.generate()); - } - - PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing(); - PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); - - progress.setProgress(R.string.progress_savingKeyRing, 90, 100); - mDatabase.saveKeyRing(secretKeyRing); - mDatabase.saveKeyRing(publicKeyRing); - - progress.setProgress(R.string.progress_done, 100, 100); - } - - public static Bundle importKeyRings(Activity context, int type, - InputData data, - ProgressDialogUpdater progress) - throws GeneralException, FileNotFoundException, PGPException, IOException { - Bundle returnData = new Bundle(); - - if (type == Id.type.secret_key) { - progress.setProgress(R.string.progress_importingSecretKeys, 0, 100); - } else { - progress.setProgress(R.string.progress_importingPublicKeys, 0, 100); - } - - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); - } - - PositionAwareInputStream progressIn = new PositionAwareInputStream(data.getInputStream()); - // need to have access to the bufferedInput, so we can reuse it for the possible - // PGPObject chunks after the first one, e.g. files with several consecutive ASCII - // armour blocks - BufferedInputStream bufferedInput = new BufferedInputStream(progressIn); - int newKeys = 0; - int oldKeys = 0; - try { - while (true) { - InputStream in = PGPUtil.getDecoderStream(bufferedInput); - PGPObjectFactory objectFactory = new PGPObjectFactory(in); - Object obj = objectFactory.nextObject(); - // if the first is already a null object, then we can stop trying - if (obj == null) { - break; - } - while (obj != null) { - PGPPublicKeyRing publicKeyRing; - PGPSecretKeyRing secretKeyRing; - // a return value that doesn't match any Id.return_value.* values, in case - // saveKeyRing is never called - int retValue = 2107; - - try { - if (type == Id.type.secret_key && obj instanceof PGPSecretKeyRing) { - secretKeyRing = (PGPSecretKeyRing) obj; - retValue = mDatabase.saveKeyRing(secretKeyRing); - } else if (type == Id.type.public_key && obj instanceof PGPPublicKeyRing) { - publicKeyRing = (PGPPublicKeyRing) obj; - retValue = mDatabase.saveKeyRing(publicKeyRing); - } - } catch (IOException e) { - retValue = Id.return_value.error; - } catch (Database.GeneralException e) { - retValue = Id.return_value.error; - } - - if (retValue == Id.return_value.error) { - throw new GeneralException(context.getString(R.string.error_savingKeys)); - } - - if (retValue == Id.return_value.updated) { - ++oldKeys; - } else if (retValue == Id.return_value.ok) { - ++newKeys; - } - progress.setProgress((int)(100 * progressIn.position() / data.getSize()), 100); - obj = objectFactory.nextObject(); - } - } - } catch (EOFException e) { - // nothing to do, we are done - } - - returnData.putInt("added", newKeys); - returnData.putInt("updated", oldKeys); - - progress.setProgress(R.string.progress_done, 100, 100); - - return returnData; - } - - public static Bundle exportKeyRings(Activity context, Vector keyRingIds, - OutputStream outStream, - ProgressDialogUpdater progress) - throws GeneralException, FileNotFoundException, PGPException, IOException { - Bundle returnData = new Bundle(); - - if (keyRingIds.size() == 1) { - progress.setProgress(R.string.progress_exportingKey, 0, 100); - } else { - progress.setProgress(R.string.progress_exportingKeys, 0, 100); - } - - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); - } - ArmoredOutputStream out = new ArmoredOutputStream(outStream); - - int numKeys = 0; - for (int i = 0; i < keyRingIds.size(); ++i) { - progress.setProgress(i * 100 / keyRingIds.size(), 100); - Object obj = mDatabase.getKeyRing(keyRingIds.get(i)); - PGPPublicKeyRing publicKeyRing; - PGPSecretKeyRing secretKeyRing; - - if (obj instanceof PGPSecretKeyRing) { - secretKeyRing = (PGPSecretKeyRing) obj; - secretKeyRing.encode(out); - } else if (obj instanceof PGPPublicKeyRing) { - publicKeyRing = (PGPPublicKeyRing) obj; - publicKeyRing.encode(out); - } else { - continue; - } - ++numKeys; - } - out.close(); - returnData.putInt("exported", numKeys); - - progress.setProgress(R.string.progress_done, 100, 100); - - return returnData; - } - - public static Date getCreationDate(PGPPublicKey key) { - return key.getCreationTime(); - } - - public static Date getCreationDate(PGPSecretKey key) { - return key.getPublicKey().getCreationTime(); - } - - public static PGPPublicKey getMasterKey(PGPPublicKeyRing keyRing) { - if (keyRing == null) { - return null; - } - for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { - if (key.isMasterKey()) { - return key; - } - } - - return null; - } - - public static PGPSecretKey getMasterKey(PGPSecretKeyRing keyRing) { - if (keyRing == null) { - return null; - } - for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { - if (key.isMasterKey()) { - return key; - } - } - - return null; - } - - public static Vector getEncryptKeys(PGPPublicKeyRing keyRing) { - Vector encryptKeys = new Vector(); - - for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { - if (isEncryptionKey(key)) { - encryptKeys.add(key); - } - } - - return encryptKeys; - } - - public static Vector getSigningKeys(PGPSecretKeyRing keyRing) { - Vector signingKeys = new Vector(); - - for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { - if (isSigningKey(key)) { - signingKeys.add(key); - } - } - - return signingKeys; - } - - public static Vector getUsableEncryptKeys(PGPPublicKeyRing keyRing) { - Vector usableKeys = new Vector(); - Vector encryptKeys = getEncryptKeys(keyRing); - PGPPublicKey masterKey = null; - for (int i = 0; i < encryptKeys.size(); ++i) { - PGPPublicKey key = encryptKeys.get(i); - if (!isExpired(key)) { - if (key.isMasterKey()) { - masterKey = key; - } else { - usableKeys.add(key); - } - } - } - if (masterKey != null) { - usableKeys.add(masterKey); - } - return usableKeys; - } - - public static boolean isExpired(PGPPublicKey key) { - Date creationDate = getCreationDate(key); - Date expiryDate = getExpiryDate(key); - Date now = new Date(); - if (now.compareTo(creationDate) >= 0 && - (expiryDate == null || now.compareTo(expiryDate) <= 0)) { - return false; - } - return true; - } - - public static boolean isExpired(PGPSecretKey key) { - return isExpired(key.getPublicKey()); - } - - public static Vector getUsableSigningKeys(PGPSecretKeyRing keyRing) { - Vector usableKeys = new Vector(); - Vector signingKeys = getSigningKeys(keyRing); - PGPSecretKey masterKey = null; - for (int i = 0; i < signingKeys.size(); ++i) { - PGPSecretKey key = signingKeys.get(i); - if (key.isMasterKey()) { - masterKey = key; - } else { - usableKeys.add(key); - } - } - if (masterKey != null) { - usableKeys.add(masterKey); - } - return usableKeys; - } - - public static Date getExpiryDate(PGPPublicKey key) { - Date creationDate = getCreationDate(key); - if (key.getValidDays() == 0) { - // no expiry - return null; - } - Calendar calendar = GregorianCalendar.getInstance(); - calendar.setTime(creationDate); - calendar.add(Calendar.DATE, key.getValidDays()); - Date expiryDate = calendar.getTime(); - - return expiryDate; - } - - public static Date getExpiryDate(PGPSecretKey key) { - return getExpiryDate(key.getPublicKey()); - } - - public static PGPPublicKey getEncryptPublicKey(long masterKeyId) { - PGPPublicKeyRing keyRing = getPublicKeyRing(masterKeyId); - if (keyRing == null) { - return null; - } - Vector encryptKeys = getUsableEncryptKeys(keyRing); - if (encryptKeys.size() == 0) { - return null; - } - return encryptKeys.get(0); - } - - public static PGPSecretKey getSigningKey(long masterKeyId) { - PGPSecretKeyRing keyRing = getSecretKeyRing(masterKeyId); - if (keyRing == null) { - return null; - } - Vector signingKeys = getUsableSigningKeys(keyRing); - if (signingKeys.size() == 0) { - return null; - } - return signingKeys.get(0); - } - - public static String getMainUserId(PGPPublicKey key) { - for (String userId : new IterableIterator(key.getUserIDs())) { - return userId; - } - return null; - } - - public static String getMainUserId(PGPSecretKey key) { - for (String userId : new IterableIterator(key.getUserIDs())) { - return userId; - } - return null; - } - - public static String getMainUserIdSafe(Context context, PGPPublicKey key) { - String userId = getMainUserId(key); - if (userId == null) { - userId = context.getResources().getString(R.string.unknownUserId); - } - return userId; - } - - public static String getMainUserIdSafe(Context context, PGPSecretKey key) { - String userId = getMainUserId(key); - if (userId == null) { - userId = context.getResources().getString(R.string.unknownUserId); - } - return userId; - } - - public static boolean isEncryptionKey(PGPPublicKey key) { - if (!key.isEncryptionKey()) { - return false; - } - - if (key.getVersion() <= 3) { - // this must be true now - return key.isEncryptionKey(); - } - - // special cases - if (key.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT) { - return true; - } - - if (key.getAlgorithm() == PGPPublicKey.RSA_ENCRYPT) { - return true; - } - - for (PGPSignature sig : new IterableIterator(key.getSignatures())) { - if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { - continue; - } - PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); - - if (hashed != null &&(hashed.getKeyFlags() & - (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) { - return true; - } - - PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); - - if (unhashed != null &&(unhashed.getKeyFlags() & - (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) { - return true; - } - } - return false; - } - - public static boolean isEncryptionKey(PGPSecretKey key) { - return isEncryptionKey(key.getPublicKey()); - } - - public static boolean isSigningKey(PGPPublicKey key) { - if (key.getVersion() <= 3) { - return true; - } - - // special case - if (key.getAlgorithm() == PGPPublicKey.RSA_SIGN) { - return true; - } - - for (PGPSignature sig : new IterableIterator(key.getSignatures())) { - if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { - continue; - } - PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); - - if (hashed != null && (hashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) { - return true; - } - - PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); - - if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) { - return true; - } - } - - return false; - } - - public static boolean isSigningKey(PGPSecretKey key) { - return isSigningKey(key.getPublicKey()); - } - - public static String getAlgorithmInfo(PGPPublicKey key) { - return getAlgorithmInfo(key.getAlgorithm(), key.getBitStrength()); - } - - public static String getAlgorithmInfo(PGPSecretKey key) { - return getAlgorithmInfo(key.getPublicKey()); - } - - public static String getAlgorithmInfo(int algorithm, int keySize) { - String algorithmStr = null; - - switch (algorithm) { - case PGPPublicKey.RSA_ENCRYPT: - case PGPPublicKey.RSA_GENERAL: - case PGPPublicKey.RSA_SIGN: { - algorithmStr = "RSA"; - break; - } - - case PGPPublicKey.DSA: { - algorithmStr = "DSA"; - break; - } - - case PGPPublicKey.ELGAMAL_ENCRYPT: - case PGPPublicKey.ELGAMAL_GENERAL: { - algorithmStr = "ElGamal"; - break; - } - - default: { - algorithmStr = "???"; - break; - } - } - return algorithmStr + ", " + keySize + "bit"; - } - - public static void deleteKey(int keyRingId) { - mDatabase.deleteKeyRing(keyRingId); - } - - public static Object getKeyRing(int keyRingId) { - return mDatabase.getKeyRing(keyRingId); - } - - public static PGPSecretKeyRing getSecretKeyRing(long keyId) { - byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_secret, keyId); - if (data == null) { - return null; - } - try { - return new PGPSecretKeyRing(data); - } catch (IOException e) { - // no good way to handle this, return null - // TODO: some info? - } catch (PGPException e) { - // no good way to handle this, return null - // TODO: some info? - } - return null; - } - - public static PGPPublicKeyRing getPublicKeyRing(long keyId) { - byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_public, keyId); - if (data == null) { - return null; - } - try { - return new PGPPublicKeyRing(data); - } catch (IOException e) { - // no good way to handle this, return null - // TODO: some info? - } - return null; - } - - public static PGPSecretKey getSecretKey(long keyId) { - PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); - if (keyRing == null) { - return null; - } - return keyRing.getSecretKey(keyId); - } - - public static PGPPublicKey getPublicKey(long keyId) { - PGPPublicKeyRing keyRing = getPublicKeyRing(keyId); - if (keyRing == null) { - return null; - } - try { - return keyRing.getPublicKey(keyId); - } catch (PGPException e) { - return null; - } - } - - public static Vector getKeyRingIds(int type) { - SQLiteDatabase db = mDatabase.db(); - Vector keyIds = new Vector(); - Cursor c = db.query(KeyRings.TABLE_NAME, - new String[] { KeyRings._ID }, - KeyRings.TYPE + " = ?", new String[] { "" + type }, - null, null, null); - if (c != null && c.moveToFirst()) { - do { - keyIds.add(c.getInt(0)); - } while (c.moveToNext()); - } - - if (c != null) { - c.close(); - } - - return keyIds; - } - - public static String getMainUserId(long keyId, int type) { - SQLiteDatabase db = mDatabase.db(); - Cursor c = db.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + - KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ") " + - " INNER JOIN " + Keys.TABLE_NAME + " AS masterKey ON (" + - KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - "masterKey." + Keys.KEY_RING_ID + " AND " + - "masterKey." + Keys.IS_MASTER_KEY + " = '1') " + - " INNER JOIN " + UserIds.TABLE_NAME + " ON (" + - UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " = " + - "masterKey." + Keys._ID + " AND " + - UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0')", - new String[] { UserIds.USER_ID }, - Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " + - KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", - new String[] { - "" + keyId, - "" + type, - }, - null, null, null); - String userId = ""; - if (c != null && c.moveToFirst()) { - do { - userId = c.getString(0); - } while (c.moveToNext()); - } - - if (c != null) { - c.close(); - } - - return userId; - } - - public static void encrypt(Context context, - InputData data, OutputStream outStream, - boolean armored, - long encryptionKeyIds[], long signatureKeyId, - String signaturePassPhrase, - ProgressDialogUpdater progress, - int symmetricAlgorithm, int hashAlgorithm, int compression, - String passPhrase) - throws IOException, GeneralException, PGPException, NoSuchProviderException, - NoSuchAlgorithmException, SignatureException { - Security.addProvider(new BouncyCastleProvider()); - - if (encryptionKeyIds == null) { - encryptionKeyIds = new long[0]; - } - - ArmoredOutputStream armorOut = null; - OutputStream out = null; - OutputStream encryptOut = null; - if (armored) { - armorOut = new ArmoredOutputStream(outStream); - armorOut.setHeader("Version", getFullVersion(context)); - out = armorOut; - } else { - out = outStream; - } - PGPSecretKey signingKey = null; - PGPSecretKeyRing signingKeyRing = null; - PGPPrivateKey signaturePrivateKey = null; - - if (encryptionKeyIds.length == 0 && passPhrase == null) { - throw new GeneralException(context.getString(R.string.error_noEncryptionKeysOrPassPhrase)); - } - - if (signatureKeyId != 0) { - signingKeyRing = getSecretKeyRing(signatureKeyId); - signingKey = getSigningKey(signatureKeyId); - if (signingKey == null) { - throw new GeneralException(context.getString(R.string.error_signatureFailed)); - } - - if (signaturePassPhrase == null) { - throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); - } - progress.setProgress(R.string.progress_extractingSignatureKey, 0, 100); - signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), - new BouncyCastleProvider()); - } - - PGPSignatureGenerator signatureGenerator = null; - progress.setProgress(R.string.progress_preparingStreams, 5, 100); - // encrypt and compress input file content - PGPEncryptedDataGenerator cPk = - new PGPEncryptedDataGenerator(symmetricAlgorithm, true, new SecureRandom(), - new BouncyCastleProvider()); - - if (encryptionKeyIds.length == 0) { - // symmetric encryption - cPk.addMethod(passPhrase.toCharArray()); - } - for (int i = 0; i < encryptionKeyIds.length; ++i) { - PGPPublicKey key = getEncryptPublicKey(encryptionKeyIds[i]); - if (key != null) { - cPk.addMethod(key); - } - } - encryptOut = cPk.open(out, new byte[1 << 16]); - - if (signatureKeyId != 0) { - progress.setProgress(R.string.progress_preparingSignature, 10, 100); - signatureGenerator = - new PGPSignatureGenerator(signingKey.getPublicKey().getAlgorithm(), - hashAlgorithm, - new BouncyCastleProvider()); - signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); - String userId = getMainUserId(getMasterKey(signingKeyRing)); - - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); - } - - PGPCompressedDataGenerator compressGen = null; - BCPGOutputStream bcpgOut = null; - if (compression == Id.choice.compression.none) { - bcpgOut = new BCPGOutputStream(encryptOut); - } else { - compressGen = new PGPCompressedDataGenerator(compression); - bcpgOut = new BCPGOutputStream(compressGen.open(encryptOut)); - } - if (signatureKeyId != 0) { - signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); - } - - PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); - // file name not needed, so empty string - OutputStream pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", - new Date(), new byte[1 << 16]); - - progress.setProgress(R.string.progress_encrypting, 20, 100); - long done = 0; - int n = 0; - byte[] buffer = new byte[1 << 16]; - InputStream in = data.getInputStream(); - while ((n = in.read(buffer)) > 0) { - pOut.write(buffer, 0, n); - if (signatureKeyId != 0) { - signatureGenerator.update(buffer, 0, n); - } - done += n; - if (data.getSize() != 0) { - progress.setProgress((int) (20 + (95 - 20) * done / data.getSize()), 100); - } - } - - literalGen.close(); - - if (signatureKeyId != 0) { - progress.setProgress(R.string.progress_generatingSignature, 95, 100); - signatureGenerator.generate().encode(pOut); - } - if (compressGen != null) { - compressGen.close(); - } - encryptOut.close(); - if (armored) { - armorOut.close(); - } - - progress.setProgress(R.string.progress_done, 100, 100); - } - - public static void signText(Context context, - InputData data, OutputStream outStream, - long signatureKeyId, String signaturePassPhrase, - int hashAlgorithm, - ProgressDialogUpdater progress) - throws GeneralException, PGPException, IOException, NoSuchAlgorithmException, - SignatureException { - Security.addProvider(new BouncyCastleProvider()); - - ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream); - armorOut.setHeader("Version", getFullVersion(context)); - - PGPSecretKey signingKey = null; - PGPSecretKeyRing signingKeyRing = null; - PGPPrivateKey signaturePrivateKey = null; - - if (signatureKeyId == 0) { - throw new GeneralException(context.getString(R.string.error_noSignatureKey)); - } - - signingKeyRing = getSecretKeyRing(signatureKeyId); - signingKey = getSigningKey(signatureKeyId); - if (signingKey == null) { - throw new GeneralException(context.getString(R.string.error_signatureFailed)); - } - - if (signaturePassPhrase == null) { - throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); - } - signaturePrivateKey = - signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), - new BouncyCastleProvider()); - - PGPSignatureGenerator signatureGenerator = null; - progress.setProgress(R.string.progress_preparingStreams, 0, 100); - - progress.setProgress(R.string.progress_preparingSignature, 30, 100); - signatureGenerator = - new PGPSignatureGenerator(signingKey.getPublicKey().getAlgorithm(), - hashAlgorithm, - new BouncyCastleProvider()); - signatureGenerator.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); - String userId = getMainUserId(getMasterKey(signingKeyRing)); - - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); - - progress.setProgress(R.string.progress_signing, 40, 100); - - armorOut.beginClearText(hashAlgorithm); - - ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); - InputStream inStream = data.getInputStream(); - int lookAhead = readInputLine(lineOut, inStream); - - processLine(armorOut, signatureGenerator, lineOut.toByteArray()); - - if (lookAhead != -1) { - do { - lookAhead = readInputLine(lineOut, lookAhead, inStream); - - signatureGenerator.update((byte)'\r'); - signatureGenerator.update((byte)'\n'); - - processLine(armorOut, signatureGenerator, lineOut.toByteArray()); - } - while (lookAhead != -1); - } - - armorOut.endClearText(); - - BCPGOutputStream bOut = new BCPGOutputStream(armorOut); - signatureGenerator.generate().encode(bOut); - armorOut.close(); - - progress.setProgress(R.string.progress_done, 100, 100); - } - - public static long getDecryptionKeyId(Context context, InputData data) - throws GeneralException, NoAsymmetricEncryptionException, IOException { - InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - PGPEncryptedDataList enc; - Object o = pgpF.nextObject(); - - // the first object might be a PGP marker packet. - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; - } else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - if (enc == null) { - throw new GeneralException(context.getString(R.string.error_invalidData)); - } - - // TODO: currently we always only look at the first known key - // find the secret key - PGPSecretKey secretKey = null; - Iterator it = enc.getEncryptedDataObjects(); - boolean gotAsymmetricEncryption = false; - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPublicKeyEncryptedData) { - gotAsymmetricEncryption = true; - PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj; - secretKey = getSecretKey(pbe.getKeyID()); - if (secretKey != null) { - break; - } - } - } - - if (!gotAsymmetricEncryption) { - throw new NoAsymmetricEncryptionException(); - } - - if (secretKey == null) { - return Id.key.none; - } - - return secretKey.getKeyID(); - } - - public static boolean hasSymmetricEncryption(Context context, InputData data) - throws GeneralException, IOException { - InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - PGPEncryptedDataList enc; - Object o = pgpF.nextObject(); - - // the first object might be a PGP marker packet. - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; - } else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - if (enc == null) { - throw new GeneralException(context.getString(R.string.error_invalidData)); - } - - Iterator it = enc.getEncryptedDataObjects(); - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPBEEncryptedData) { - return true; - } - } - - return false; - } - - public static Bundle decrypt(Context context, - InputData data, OutputStream outStream, - String passPhrase, ProgressDialogUpdater progress, - boolean assumeSymmetric) - throws IOException, GeneralException, PGPException, SignatureException { - if (passPhrase == null) { - passPhrase = ""; - } - Bundle returnData = new Bundle(); - InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - PGPEncryptedDataList enc; - Object o = pgpF.nextObject(); - long signatureKeyId = 0; - - int currentProgress = 0; - progress.setProgress(R.string.progress_readingData, currentProgress, 100); - - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; - } else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - if (enc == null) { - throw new GeneralException(context.getString(R.string.error_invalidData)); - } - - InputStream clear = null; - PGPEncryptedData encryptedData = null; - - currentProgress += 5; - - // TODO: currently we always only look at the first known key or symmetric encryption, - // there might be more... - if (assumeSymmetric) { - PGPPBEEncryptedData pbe = null; - Iterator it = enc.getEncryptedDataObjects(); - // find secret key - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPBEEncryptedData) { - pbe = (PGPPBEEncryptedData) obj; - break; - } - } - - if (pbe == null) { - throw new GeneralException(context.getString(R.string.error_noSymmetricEncryptionPacket)); - } - - progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100); - clear = pbe.getDataStream(passPhrase.toCharArray(), new BouncyCastleProvider()); - encryptedData = pbe; - currentProgress += 5; - } else { - progress.setProgress(R.string.progress_findingKey, currentProgress, 100); - PGPPublicKeyEncryptedData pbe = null; - PGPSecretKey secretKey = null; - Iterator it = enc.getEncryptedDataObjects(); - // find secret key - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPublicKeyEncryptedData) { - PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; - secretKey = getSecretKey(encData.getKeyID()); - if (secretKey != null) { - pbe = encData; - break; - } - } - } - - if (secretKey == null) { - throw new GeneralException(context.getString(R.string.error_noSecretKeyFound)); - } - - currentProgress += 5; - progress.setProgress(R.string.progress_extractingKey, currentProgress, 100); - PGPPrivateKey privateKey = null; - try { - privateKey = secretKey.extractPrivateKey(passPhrase.toCharArray(), - new BouncyCastleProvider()); - } catch (PGPException e) { - throw new PGPException(context.getString(R.string.error_wrongPassPhrase)); - } - currentProgress += 5; - progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100); - clear = pbe.getDataStream(privateKey, new BouncyCastleProvider()); - encryptedData = pbe; - currentProgress += 5; - } - - PGPObjectFactory plainFact = new PGPObjectFactory(clear); - Object dataChunk = plainFact.nextObject(); - PGPOnePassSignature signature = null; - PGPPublicKey signatureKey = null; - int signatureIndex = -1; - - if (dataChunk instanceof PGPCompressedData) { - progress.setProgress(R.string.progress_decompressingData, currentProgress, 100); - PGPObjectFactory fact = - new PGPObjectFactory(((PGPCompressedData) dataChunk).getDataStream()); - dataChunk = fact.nextObject(); - plainFact = fact; - currentProgress += 10; - } - - if (dataChunk instanceof PGPOnePassSignatureList) { - progress.setProgress(R.string.progress_processingSignature, currentProgress, 100); - returnData.putBoolean(EXTRA_SIGNATURE, true); - PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; - for (int i = 0; i < sigList.size(); ++i) { - signature = sigList.get(i); - signatureKey = getPublicKey(signature.getKeyID()); - if (signatureKeyId == 0) { - signatureKeyId = signature.getKeyID(); - } - if (signatureKey == null) { - signature = null; - } else { - signatureIndex = i; - signatureKeyId = signature.getKeyID(); - String userId = null; - PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); - if (sigKeyRing != null) { - userId = getMainUserId(getMasterKey(sigKeyRing)); - } - returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); - break; - } - } - - returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); - - if (signature != null) { - signature.initVerify(signatureKey, new BouncyCastleProvider()); - } else { - returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true); - } - - dataChunk = plainFact.nextObject(); - currentProgress += 10; - } - - if (dataChunk instanceof PGPLiteralData) { - progress.setProgress(R.string.progress_decrypting, currentProgress, 100); - PGPLiteralData literalData = (PGPLiteralData) dataChunk; - OutputStream out = outStream; - - byte[] buffer = new byte[1 << 16]; - InputStream dataIn = literalData.getInputStream(); - - int startProgress = currentProgress; - int endProgress = 100; - if (signature != null) { - endProgress = 90; - } else if (encryptedData.isIntegrityProtected()) { - endProgress = 95; - } - int n = 0; - int done = 0; - long startPos = data.getStreamPosition(); - while ((n = dataIn.read(buffer)) > 0) { - out.write(buffer, 0, n); - done += n; - if (signature != null) { - try { - signature.update(buffer, 0, n); - } catch (SignatureException e) { - returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, false); - signature = null; - } - } - // unknown size, but try to at least have a moving, slowing down progress bar - currentProgress = startProgress + (endProgress - startProgress) * done / (done + 100000); - if (data.getSize() - startPos == 0) { - currentProgress = endProgress; - } else { - currentProgress = (int)(startProgress + (endProgress - startProgress) * - (data.getStreamPosition() - startPos) / (data.getSize() - startPos)); - } - progress.setProgress(currentProgress, 100); - } - - if (signature != null) { - progress.setProgress(R.string.progress_verifyingSignature, 90, 100); - PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); - PGPSignature messageSignature = (PGPSignature) signatureList.get(signatureIndex); - if (signature.verify(messageSignature)) { - returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, true); - } else { - returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, false); - } - } - } - - // TODO: add integrity somewhere - if (encryptedData.isIntegrityProtected()) { - progress.setProgress(R.string.progress_verifyingIntegrity, 95, 100); - if (encryptedData.verify()) { - // passed - } else { - // failed - } - } else { - // no integrity check - } - - progress.setProgress(R.string.progress_done, 100, 100); - return returnData; - } - - public static Bundle verifyText(Context context, - InputData data, OutputStream outStream, - ProgressDialogUpdater progress) - throws IOException, GeneralException, PGPException, SignatureException { - Bundle returnData = new Bundle(); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ArmoredInputStream aIn = new ArmoredInputStream(data.getInputStream()); - - progress.setProgress(R.string.progress_done, 0, 100); - - // mostly taken from ClearSignedFileProcessor - ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); - int lookAhead = readInputLine(lineOut, aIn); - byte[] lineSep = getLineSeparator(); - - byte[] line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparator(line)); - out.write(lineSep); - - while (lookAhead != -1 && aIn.isClearText()) { - lookAhead = readInputLine(lineOut, lookAhead, aIn); - line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparator(line)); - out.write(lineSep); - } - - out.close(); - - byte[] clearText = out.toByteArray(); - outStream.write(clearText); - - returnData.putBoolean(EXTRA_SIGNATURE, true); - - progress.setProgress(R.string.progress_processingSignature, 60, 100); - PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); - - PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); - if (sigList == null) { - throw new GeneralException(context.getString(R.string.error_corruptData)); - } - PGPSignature signature = null; - long signatureKeyId = 0; - PGPPublicKey signatureKey = null; - for (int i = 0; i < sigList.size(); ++i) { - signature = sigList.get(i); - signatureKey = getPublicKey(signature.getKeyID()); - if (signatureKeyId == 0) { - signatureKeyId = signature.getKeyID(); - } - if (signatureKey == null) { - signature = null; - } else { - signatureKeyId = signature.getKeyID(); - String userId = null; - PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); - if (sigKeyRing != null) { - userId = getMainUserId(getMasterKey(sigKeyRing)); - } - returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); - break; - } - } - - returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); - - if (signature == null) { - returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true); - progress.setProgress(R.string.progress_done, 100, 100); - return returnData; - } - - signature.initVerify(signatureKey, new BouncyCastleProvider()); - - InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText)); - - lookAhead = readInputLine(lineOut, sigIn); - - processLine(signature, lineOut.toByteArray()); - - if (lookAhead != -1) { - do { - lookAhead = readInputLine(lineOut, lookAhead, sigIn); - - signature.update((byte)'\r'); - signature.update((byte)'\n'); - - processLine(signature, lineOut.toByteArray()); - } - while (lookAhead != -1); - } - - returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, signature.verify()); - - progress.setProgress(R.string.progress_done, 100, 100); - return returnData; - } - - public static int getStreamContent(Context context, InputStream inStream) - throws IOException { - InputStream in = PGPUtil.getDecoderStream(inStream); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - Object object = pgpF.nextObject(); - while (object != null) { - if (object instanceof PGPPublicKeyRing || - object instanceof PGPSecretKeyRing) { - return Id.content.keys; - } else if (object instanceof PGPEncryptedDataList) { - return Id.content.encrypted_data; - } - object = pgpF.nextObject(); - } - - return Id.content.unknown; - } - - // taken from ClearSignedFileProcessor in BC - private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) - throws IOException { - bOut.reset(); - - int lookAhead = -1; - int ch; - - while ((ch = fIn.read()) >= 0) { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } - - return lookAhead; - } - - private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) - throws IOException { - bOut.reset(); - - int ch = lookAhead; - - do { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } - while ((ch = fIn.read()) >= 0); - - if (ch < 0) { - lookAhead = -1; - } - - return lookAhead; - } - - private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) - throws IOException { - int lookAhead = fIn.read(); - - if (lastCh == '\r' && lookAhead == '\n') { - bOut.write(lookAhead); - lookAhead = fIn.read(); - } - - return lookAhead; - } - - private static void processLine(PGPSignature sig, byte[] line) - throws SignatureException, IOException { - int length = getLengthWithoutWhiteSpace(line); - if (length > 0) { - sig.update(line, 0, length); - } - } - - private static void processLine(OutputStream aOut, PGPSignatureGenerator sGen, byte[] line) - throws SignatureException, IOException { - int length = getLengthWithoutWhiteSpace(line); - if (length > 0) { - sGen.update(line, 0, length); - } - - aOut.write(line, 0, line.length); - } - - private static int getLengthWithoutSeparator(byte[] line) { - int end = line.length - 1; - - while (end >= 0 && isLineEnding(line[end])) { - end--; - } - - return end + 1; - } - - private static boolean isLineEnding(byte b) { - return b == '\r' || b == '\n'; - } - - private static int getLengthWithoutWhiteSpace(byte[] line) { - int end = line.length - 1; - - while (end >= 0 && isWhiteSpace(line[end])) { - end--; - } - - return end + 1; - } - - private static boolean isWhiteSpace(byte b) { - return b == '\r' || b == '\n' || b == '\t' || b == ' '; - } - - private static byte[] getLineSeparator() { - String nl = System.getProperty("line.separator"); - byte[] nlBytes = new byte[nl.length()]; - - for (int i = 0; i != nlBytes.length; i++) { - nlBytes[i] = (byte)nl.charAt(i); - } - - return nlBytes; - } - - public static String getVersion(Context context) { - if (VERSION != null) { - return VERSION; - } - try { - PackageInfo pi = context.getPackageManager().getPackageInfo(mApgPackageName, 0); - VERSION = pi.versionName; - return VERSION; - } catch (NameNotFoundException e) { - // unpossible! - return "0.0.0"; - } - } - - public static String getFullVersion(Context context) { - return "APG v" + getVersion(context); - } - - public static String generateRandomString(int length) { - SecureRandom random = new SecureRandom(); - /* - try { - random = SecureRandom.getInstance("SHA1PRNG", new BouncyCastleProvider()); - } catch (NoSuchAlgorithmException e) { - // TODO: need to handle this case somehow - return null; - }*/ - byte bytes[] = new byte[length]; - random.nextBytes(bytes); - String result = ""; - for (int i = 0; i < length; ++i) { - int v = ((int)bytes[i] + 256) % 64; - if (v < 10) { - result += (char)((int)'0' + v); - } else if (v < 36) { - result += (char)((int)'A' + v - 10); - } else if (v < 62) { - result += (char)((int)'a' + v - 36); - } else if (v == 62) { - result += '_'; - } else if (v == 63) { - result += '.'; - } - } - return result; - } - - static long getLengthOfStream(InputStream in) throws IOException { - long size = 0; - long n = 0; - byte dummy[] = new byte[0x10000]; - while ((n = in.read(dummy)) > 0) { - size += n; - } - return size; - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; +import java.security.SignatureException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Vector; +import java.util.regex.Pattern; + +import org.bouncycastle2.bcpg.ArmoredInputStream; +import org.bouncycastle2.bcpg.ArmoredOutputStream; +import org.bouncycastle2.bcpg.BCPGOutputStream; +import org.bouncycastle2.bcpg.CompressionAlgorithmTags; +import org.bouncycastle2.bcpg.HashAlgorithmTags; +import org.bouncycastle2.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle2.bcpg.sig.KeyFlags; +import org.bouncycastle2.jce.provider.BouncyCastleProvider; +import org.bouncycastle2.jce.spec.ElGamalParameterSpec; +import org.bouncycastle2.openpgp.PGPCompressedData; +import org.bouncycastle2.openpgp.PGPCompressedDataGenerator; +import org.bouncycastle2.openpgp.PGPEncryptedData; +import org.bouncycastle2.openpgp.PGPEncryptedDataGenerator; +import org.bouncycastle2.openpgp.PGPEncryptedDataList; +import org.bouncycastle2.openpgp.PGPException; +import org.bouncycastle2.openpgp.PGPKeyPair; +import org.bouncycastle2.openpgp.PGPKeyRingGenerator; +import org.bouncycastle2.openpgp.PGPLiteralData; +import org.bouncycastle2.openpgp.PGPLiteralDataGenerator; +import org.bouncycastle2.openpgp.PGPObjectFactory; +import org.bouncycastle2.openpgp.PGPOnePassSignature; +import org.bouncycastle2.openpgp.PGPOnePassSignatureList; +import org.bouncycastle2.openpgp.PGPPBEEncryptedData; +import org.bouncycastle2.openpgp.PGPPrivateKey; +import org.bouncycastle2.openpgp.PGPPublicKey; +import org.bouncycastle2.openpgp.PGPPublicKeyEncryptedData; +import org.bouncycastle2.openpgp.PGPPublicKeyRing; +import org.bouncycastle2.openpgp.PGPSecretKey; +import org.bouncycastle2.openpgp.PGPSecretKeyRing; +import org.bouncycastle2.openpgp.PGPSignature; +import org.bouncycastle2.openpgp.PGPSignatureGenerator; +import org.bouncycastle2.openpgp.PGPSignatureList; +import org.bouncycastle2.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle2.openpgp.PGPSignatureSubpacketVector; +import org.bouncycastle2.openpgp.PGPUtil; +import org.thialfihar.android.apg.provider.DataProvider; +import org.thialfihar.android.apg.provider.Database; +import org.thialfihar.android.apg.provider.KeyRings; +import org.thialfihar.android.apg.provider.Keys; +import org.thialfihar.android.apg.provider.UserIds; +import org.thialfihar.android.apg.ui.widget.KeyEditor; +import org.thialfihar.android.apg.ui.widget.SectionView; +import org.thialfihar.android.apg.ui.widget.UserIdEditor; +import org.thialfihar.android.apg.utils.IterableIterator; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.view.ViewGroup; + +public class Apg { + private static final String mApgPackageName = "org.thialfihar.android.apg"; + + public static class Intent { + public static final String DECRYPT = "org.thialfihar.android.apg.intent.DECRYPT"; + public static final String ENCRYPT = "org.thialfihar.android.apg.intent.ENCRYPT"; + public static final String DECRYPT_FILE = "org.thialfihar.android.apg.intent.DECRYPT_FILE"; + public static final String ENCRYPT_FILE = "org.thialfihar.android.apg.intent.ENCRYPT_FILE"; + public static final String DECRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.DECRYPT_AND_RETURN"; + public static final String ENCRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.ENCRYPT_AND_RETURN"; + public static final String SELECT_PUBLIC_KEYS = "org.thialfihar.android.apg.intent.SELECT_PUBLIC_KEYS"; + public static final String SELECT_SECRET_KEY = "org.thialfihar.android.apg.intent.SELECT_SECRET_KEY"; + public static final String IMPORT = "org.thialfihar.android.apg.intent.IMPORT"; + } + + public static final String EXTRA_TEXT = "text"; + public static final String EXTRA_DATA = "data"; + public static final String EXTRA_STATUS = "status"; + public static final String EXTRA_ERROR = "error"; + public static final String EXTRA_DECRYPTED_MESSAGE = "decryptedMessage"; + public static final String EXTRA_DECRYPTED_DATA = "decryptedData"; + public static final String EXTRA_ENCRYPTED_MESSAGE = "encryptedMessage"; + public static final String EXTRA_ENCRYPTED_DATA = "encryptedData"; + public static final String EXTRA_RESULT_URI = "resultUri"; + public static final String EXTRA_SIGNATURE = "signature"; + public static final String EXTRA_SIGNATURE_KEY_ID = "signatureKeyId"; + public static final String EXTRA_SIGNATURE_USER_ID = "signatureUserId"; + public static final String EXTRA_SIGNATURE_SUCCESS = "signatureSuccess"; + public static final String EXTRA_SIGNATURE_UNKNOWN = "signatureUnknown"; + public static final String EXTRA_USER_ID = "userId"; + public static final String EXTRA_KEY_ID = "keyId"; + public static final String EXTRA_REPLY_TO = "replyTo"; + public static final String EXTRA_SEND_TO = "sendTo"; + public static final String EXTRA_SUBJECT = "subject"; + public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryptionKeyIds"; + public static final String EXTRA_SELECTION = "selection"; + public static final String EXTRA_MESSAGE = "message"; + public static final String EXTRA_PROGRESS = "progress"; + public static final String EXTRA_MAX = "max"; + public static final String EXTRA_ACCOUNT = "account"; + public static final String EXTRA_ASCII_ARMOUR = "asciiArmour"; + public static final String EXTRA_BINARY = "binary"; + + public static final String AUTHORITY = DataProvider.AUTHORITY; + + public static final Uri CONTENT_URI_SECRET_KEY_RINGS = + Uri.parse("content://" + AUTHORITY + "/key_rings/secret/"); + public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID = + Uri.parse("content://" + AUTHORITY + "/key_rings/secret/key_id/"); + public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_EMAILS = + Uri.parse("content://" + AUTHORITY + "/key_rings/secret/emails/"); + + public static final Uri CONTENT_URI_PUBLIC_KEY_RINGS = + Uri.parse("content://" + AUTHORITY + "/key_rings/public/"); + public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_KEY_ID = + Uri.parse("content://" + AUTHORITY + "/key_rings/public/key_id/"); + public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS = + Uri.parse("content://" + AUTHORITY + "/key_rings/public/emails/"); + + private static String VERSION = null; + + private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = + new int[] { + SymmetricKeyAlgorithmTags.AES_256, + SymmetricKeyAlgorithmTags.AES_192, + SymmetricKeyAlgorithmTags.AES_128, + SymmetricKeyAlgorithmTags.CAST5, + SymmetricKeyAlgorithmTags.TRIPLE_DES }; + private static final int[] PREFERRED_HASH_ALGORITHMS = + new int[] { + HashAlgorithmTags.SHA1, + HashAlgorithmTags.SHA256, + HashAlgorithmTags.RIPEMD160 }; + private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = + new int[] { + CompressionAlgorithmTags.ZLIB, + CompressionAlgorithmTags.BZIP2, + CompressionAlgorithmTags.ZIP }; + + public static Pattern PGP_MESSAGE = + Pattern.compile(".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", + Pattern.DOTALL); + + public static Pattern PGP_SIGNED_MESSAGE = + Pattern.compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", + Pattern.DOTALL); + + private static HashMap mPassPhraseCache = + new HashMap(); + private static String mEditPassPhrase = null; + + private static Database mDatabase = null; + + public static class GeneralException extends Exception { + static final long serialVersionUID = 0xf812773342L; + + public GeneralException(String message) { + super(message); + } + } + + public static class NoAsymmetricEncryptionException extends Exception { + static final long serialVersionUID = 0xf812773343L; + + public NoAsymmetricEncryptionException() { + super(); + } + } + + public static void initialize(Context context) { + if (mDatabase == null) { + mDatabase = new Database(context); + } + } + + public static Database getDatabase() { + return mDatabase; + } + + public static void setEditPassPhrase(String passPhrase) { + mEditPassPhrase = passPhrase; + } + + public static String getEditPassPhrase() { + return mEditPassPhrase; + } + + public static void setCachedPassPhrase(long keyId, String passPhrase) { + mPassPhraseCache.put(keyId, new CachedPassPhrase(new Date().getTime(), passPhrase)); + } + + public static String getCachedPassPhrase(long keyId) { + long realId = keyId; + if (realId != Id.key.symmetric) { + PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); + if (keyRing == null) { + return null; + } + PGPSecretKey masterKey = getMasterKey(keyRing); + if (masterKey == null) { + return null; + } + realId = masterKey.getKeyID(); + } + CachedPassPhrase cpp = mPassPhraseCache.get(realId); + if (cpp == null) { + return null; + } + // set it again to reset the cache life cycle + setCachedPassPhrase(realId, cpp.passPhrase); + return cpp.passPhrase; + } + + public static int cleanUpCache(int ttl, int initialDelay) { + int delay = initialDelay; + long realTtl = ttl * 1000; + long now = new Date().getTime(); + Vector oldKeys = new Vector(); + for (Map.Entry pair : mPassPhraseCache.entrySet()) { + long lived = now - pair.getValue().timestamp; + if (lived >= realTtl) { + oldKeys.add(pair.getKey()); + } else { + // see, whether the remaining time for this cache entry improves our + // check delay + long nextCheck = realTtl - lived + 1000; + if (nextCheck < delay) { + delay = (int)nextCheck; + } + } + } + + for (long keyId : oldKeys) { + mPassPhraseCache.remove(keyId); + } + + return delay; + } + + public static PGPSecretKey createKey(Context context, + int algorithmChoice, int keySize, String passPhrase, + PGPSecretKey masterKey) + throws NoSuchAlgorithmException, PGPException, NoSuchProviderException, + GeneralException, InvalidAlgorithmParameterException { + + if (keySize < 512) { + throw new GeneralException(context.getString(R.string.error_keySizeMinimum512bit)); + } + + Security.addProvider(new BouncyCastleProvider()); + + if (passPhrase == null) { + passPhrase = ""; + } + + int algorithm = 0; + KeyPairGenerator keyGen = null; + + switch (algorithmChoice) { + case Id.choice.algorithm.dsa: { + keyGen = KeyPairGenerator.getInstance("DSA", new BouncyCastleProvider()); + keyGen.initialize(keySize, new SecureRandom()); + algorithm = PGPPublicKey.DSA; + break; + } + + case Id.choice.algorithm.elgamal: { + if (masterKey == null) { + throw new GeneralException(context.getString(R.string.error_masterKeyMustNotBeElGamal)); + } + keyGen = KeyPairGenerator.getInstance("ELGAMAL", new BouncyCastleProvider()); + BigInteger p = Primes.getBestPrime(keySize); + BigInteger g = new BigInteger("2"); + + ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g); + + keyGen.initialize(elParams); + algorithm = PGPPublicKey.ELGAMAL_ENCRYPT; + break; + } + + case Id.choice.algorithm.rsa: { + keyGen = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider()); + keyGen.initialize(keySize, new SecureRandom()); + + algorithm = PGPPublicKey.RSA_GENERAL; + break; + } + + default: { + throw new GeneralException(context.getString(R.string.error_unknownAlgorithmChoice)); + } + } + + PGPKeyPair keyPair = new PGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date()); + + PGPSecretKey secretKey = null; + if (masterKey == null) { + // enough for now, as we assemble the key again later anyway + secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, keyPair, "", + PGPEncryptedData.CAST5, passPhrase.toCharArray(), + null, null, + new SecureRandom(), new BouncyCastleProvider().getName()); + + } else { + PGPPublicKey tmpKey = masterKey.getPublicKey(); + PGPPublicKey masterPublicKey = + new PGPPublicKey(tmpKey.getAlgorithm(), + tmpKey.getKey(new BouncyCastleProvider()), + tmpKey.getCreationTime()); + PGPPrivateKey masterPrivateKey = + masterKey.extractPrivateKey(passPhrase.toCharArray(), + new BouncyCastleProvider()); + + PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); + PGPKeyRingGenerator ringGen = + new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, + masterKeyPair, "", + PGPEncryptedData.CAST5, passPhrase.toCharArray(), + null, null, + new SecureRandom(), new BouncyCastleProvider().getName()); + ringGen.addSubKey(keyPair); + PGPSecretKeyRing secKeyRing = ringGen.generateSecretKeyRing(); + Iterator it = secKeyRing.getSecretKeys(); + // first one is the master key + it.next(); + secretKey = (PGPSecretKey) it.next(); + } + + return secretKey; + } + + private static long getNumDaysBetween(GregorianCalendar first, GregorianCalendar second) { + GregorianCalendar tmp = new GregorianCalendar(); + tmp.setTime(first.getTime()); + long numDays = (second.getTimeInMillis() - first.getTimeInMillis()) / 1000 / 86400; + tmp.add(Calendar.DAY_OF_MONTH, (int)numDays); + while (tmp.before(second)) { + tmp.add(Calendar.DAY_OF_MONTH, 1); + ++numDays; + } + return numDays; + } + + public static void buildSecretKey(Activity context, + SectionView userIdsView, SectionView keysView, + String oldPassPhrase, String newPassPhrase, + ProgressDialogUpdater progress) + throws Apg.GeneralException, NoSuchProviderException, PGPException, + NoSuchAlgorithmException, SignatureException, IOException, Database.GeneralException { + + progress.setProgress(R.string.progress_buildingKey, 0, 100); + + Security.addProvider(new BouncyCastleProvider()); + + if (oldPassPhrase == null || oldPassPhrase.equals("")) { + oldPassPhrase = ""; + } + + if (newPassPhrase == null || newPassPhrase.equals("")) { + newPassPhrase = ""; + } + + Vector userIds = new Vector(); + Vector keys = new Vector(); + + ViewGroup userIdEditors = userIdsView.getEditors(); + ViewGroup keyEditors = keysView.getEditors(); + + boolean gotMainUserId = false; + for (int i = 0; i < userIdEditors.getChildCount(); ++i) { + UserIdEditor editor = (UserIdEditor)userIdEditors.getChildAt(i); + String userId = null; + try { + userId = editor.getValue(); + } catch (UserIdEditor.NoNameException e) { + throw new Apg.GeneralException(context.getString(R.string.error_userIdNeedsAName)); + } catch (UserIdEditor.NoEmailException e) { + throw new Apg.GeneralException(context.getString(R.string.error_userIdNeedsAnEmailAddress)); + } catch (UserIdEditor.InvalidEmailException e) { + throw new Apg.GeneralException("" + e); + } + + if (userId.equals("")) { + continue; + } + + if (editor.isMainUserId()) { + userIds.insertElementAt(userId, 0); + gotMainUserId = true; + } else { + userIds.add(userId); + } + } + + if (userIds.size() == 0) { + throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsAUserId)); + } + + if (!gotMainUserId) { + throw new Apg.GeneralException(context.getString(R.string.error_mainUserIdMustNotBeEmpty)); + } + + if (keyEditors.getChildCount() == 0) { + throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsMasterKey)); + } + + for (int i = 0; i < keyEditors.getChildCount(); ++i) { + KeyEditor editor = (KeyEditor)keyEditors.getChildAt(i); + keys.add(editor.getValue()); + } + + progress.setProgress(R.string.progress_preparingMasterKey, 10, 100); + KeyEditor keyEditor = (KeyEditor) keyEditors.getChildAt(0); + int usageId = keyEditor.getUsage(); + boolean canSign = (usageId == Id.choice.usage.sign_only || + usageId == Id.choice.usage.sign_and_encrypt); + boolean canEncrypt = (usageId == Id.choice.usage.encrypt_only || + usageId == Id.choice.usage.sign_and_encrypt); + + String mainUserId = userIds.get(0); + + PGPSecretKey masterKey = keys.get(0); + PGPPublicKey tmpKey = masterKey.getPublicKey(); + PGPPublicKey masterPublicKey = + new PGPPublicKey(tmpKey.getAlgorithm(), + tmpKey.getKey(new BouncyCastleProvider()), + tmpKey.getCreationTime()); + PGPPrivateKey masterPrivateKey = + masterKey.extractPrivateKey(oldPassPhrase.toCharArray(), + new BouncyCastleProvider()); + + progress.setProgress(R.string.progress_certifyingMasterKey, 20, 100); + for (int i = 0; i < userIds.size(); ++i) { + String userId = userIds.get(i); + + PGPSignatureGenerator sGen = + new PGPSignatureGenerator(masterPublicKey.getAlgorithm(), + HashAlgorithmTags.SHA1, new BouncyCastleProvider()); + + sGen.initSign(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); + + PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); + + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification); + } + + // TODO: cross-certify the master key with every sub key + + PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); + + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); + + int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA; + if (canEncrypt) { + keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; + } + hashedPacketsGen.setKeyFlags(true, keyFlags); + + hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); + hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); + hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); + + // TODO: this doesn't work quite right yet + if (keyEditor.getExpiryDate() != null) { + GregorianCalendar creationDate = new GregorianCalendar(); + creationDate.setTime(getCreationDate(masterKey)); + GregorianCalendar expiryDate = keyEditor.getExpiryDate(); + long numDays = getNumDaysBetween(creationDate, expiryDate); + if (numDays <= 0) { + throw new GeneralException(context.getString(R.string.error_expiryMustComeAfterCreation)); + } + hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400); + } + + progress.setProgress(R.string.progress_buildingMasterKeyRing, 30, 100); + PGPKeyRingGenerator keyGen = + new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, + masterKeyPair, mainUserId, + PGPEncryptedData.CAST5, newPassPhrase.toCharArray(), + hashedPacketsGen.generate(), unhashedPacketsGen.generate(), + new SecureRandom(), new BouncyCastleProvider().getName()); + + progress.setProgress(R.string.progress_addingSubKeys, 40, 100); + for (int i = 1; i < keys.size(); ++i) { + progress.setProgress(40 + 50 * (i - 1)/ (keys.size() - 1), 100); + PGPSecretKey subKey = keys.get(i); + keyEditor = (KeyEditor) keyEditors.getChildAt(i); + PGPPublicKey subPublicKey = subKey.getPublicKey(); + PGPPrivateKey subPrivateKey = + subKey.extractPrivateKey(oldPassPhrase.toCharArray(), + new BouncyCastleProvider()); + PGPKeyPair subKeyPair = + new PGPKeyPair(subPublicKey.getAlgorithm(), + subPublicKey.getKey(new BouncyCastleProvider()), + subPrivateKey.getKey(), + subPublicKey.getCreationTime()); + + hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); + + keyFlags = 0; + usageId = keyEditor.getUsage(); + canSign = (usageId == Id.choice.usage.sign_only || + usageId == Id.choice.usage.sign_and_encrypt); + canEncrypt = (usageId == Id.choice.usage.encrypt_only || + usageId == Id.choice.usage.sign_and_encrypt); + if (canSign) { + keyFlags |= KeyFlags.SIGN_DATA; + } + if (canEncrypt) { + keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; + } + hashedPacketsGen.setKeyFlags(true, keyFlags); + + // TODO: this doesn't work quite right yet + if (keyEditor.getExpiryDate() != null) { + GregorianCalendar creationDate = new GregorianCalendar(); + creationDate.setTime(getCreationDate(masterKey)); + GregorianCalendar expiryDate = keyEditor.getExpiryDate(); + long numDays = getNumDaysBetween(creationDate, expiryDate); + if (numDays <= 0) { + throw new GeneralException(context.getString(R.string.error_expiryMustComeAfterCreation)); + } + hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400); + } + + keyGen.addSubKey(subKeyPair, + hashedPacketsGen.generate(), unhashedPacketsGen.generate()); + } + + PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing(); + PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); + + progress.setProgress(R.string.progress_savingKeyRing, 90, 100); + mDatabase.saveKeyRing(secretKeyRing); + mDatabase.saveKeyRing(publicKeyRing); + + progress.setProgress(R.string.progress_done, 100, 100); + } + + public static Bundle importKeyRings(Activity context, int type, + InputData data, + ProgressDialogUpdater progress) + throws GeneralException, FileNotFoundException, PGPException, IOException { + Bundle returnData = new Bundle(); + + if (type == Id.type.secret_key) { + progress.setProgress(R.string.progress_importingSecretKeys, 0, 100); + } else { + progress.setProgress(R.string.progress_importingPublicKeys, 0, 100); + } + + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); + } + + PositionAwareInputStream progressIn = new PositionAwareInputStream(data.getInputStream()); + // need to have access to the bufferedInput, so we can reuse it for the possible + // PGPObject chunks after the first one, e.g. files with several consecutive ASCII + // armour blocks + BufferedInputStream bufferedInput = new BufferedInputStream(progressIn); + int newKeys = 0; + int oldKeys = 0; + try { + while (true) { + InputStream in = PGPUtil.getDecoderStream(bufferedInput); + PGPObjectFactory objectFactory = new PGPObjectFactory(in); + Object obj = objectFactory.nextObject(); + // if the first is already a null object, then we can stop trying + if (obj == null) { + break; + } + while (obj != null) { + PGPPublicKeyRing publicKeyRing; + PGPSecretKeyRing secretKeyRing; + // a return value that doesn't match any Id.return_value.* values, in case + // saveKeyRing is never called + int retValue = 2107; + + try { + if (type == Id.type.secret_key && obj instanceof PGPSecretKeyRing) { + secretKeyRing = (PGPSecretKeyRing) obj; + retValue = mDatabase.saveKeyRing(secretKeyRing); + } else if (type == Id.type.public_key && obj instanceof PGPPublicKeyRing) { + publicKeyRing = (PGPPublicKeyRing) obj; + retValue = mDatabase.saveKeyRing(publicKeyRing); + } + } catch (IOException e) { + retValue = Id.return_value.error; + } catch (Database.GeneralException e) { + retValue = Id.return_value.error; + } + + if (retValue == Id.return_value.error) { + throw new GeneralException(context.getString(R.string.error_savingKeys)); + } + + if (retValue == Id.return_value.updated) { + ++oldKeys; + } else if (retValue == Id.return_value.ok) { + ++newKeys; + } + progress.setProgress((int)(100 * progressIn.position() / data.getSize()), 100); + obj = objectFactory.nextObject(); + } + } + } catch (EOFException e) { + // nothing to do, we are done + } + + returnData.putInt("added", newKeys); + returnData.putInt("updated", oldKeys); + + progress.setProgress(R.string.progress_done, 100, 100); + + return returnData; + } + + public static Bundle exportKeyRings(Activity context, Vector keyRingIds, + OutputStream outStream, + ProgressDialogUpdater progress) + throws GeneralException, FileNotFoundException, PGPException, IOException { + Bundle returnData = new Bundle(); + + if (keyRingIds.size() == 1) { + progress.setProgress(R.string.progress_exportingKey, 0, 100); + } else { + progress.setProgress(R.string.progress_exportingKeys, 0, 100); + } + + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); + } + ArmoredOutputStream out = new ArmoredOutputStream(outStream); + + int numKeys = 0; + for (int i = 0; i < keyRingIds.size(); ++i) { + progress.setProgress(i * 100 / keyRingIds.size(), 100); + Object obj = mDatabase.getKeyRing(keyRingIds.get(i)); + PGPPublicKeyRing publicKeyRing; + PGPSecretKeyRing secretKeyRing; + + if (obj instanceof PGPSecretKeyRing) { + secretKeyRing = (PGPSecretKeyRing) obj; + secretKeyRing.encode(out); + } else if (obj instanceof PGPPublicKeyRing) { + publicKeyRing = (PGPPublicKeyRing) obj; + publicKeyRing.encode(out); + } else { + continue; + } + ++numKeys; + } + out.close(); + returnData.putInt("exported", numKeys); + + progress.setProgress(R.string.progress_done, 100, 100); + + return returnData; + } + + public static Date getCreationDate(PGPPublicKey key) { + return key.getCreationTime(); + } + + public static Date getCreationDate(PGPSecretKey key) { + return key.getPublicKey().getCreationTime(); + } + + public static PGPPublicKey getMasterKey(PGPPublicKeyRing keyRing) { + if (keyRing == null) { + return null; + } + for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { + if (key.isMasterKey()) { + return key; + } + } + + return null; + } + + public static PGPSecretKey getMasterKey(PGPSecretKeyRing keyRing) { + if (keyRing == null) { + return null; + } + for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { + if (key.isMasterKey()) { + return key; + } + } + + return null; + } + + public static Vector getEncryptKeys(PGPPublicKeyRing keyRing) { + Vector encryptKeys = new Vector(); + + for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { + if (isEncryptionKey(key)) { + encryptKeys.add(key); + } + } + + return encryptKeys; + } + + public static Vector getSigningKeys(PGPSecretKeyRing keyRing) { + Vector signingKeys = new Vector(); + + for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { + if (isSigningKey(key)) { + signingKeys.add(key); + } + } + + return signingKeys; + } + + public static Vector getUsableEncryptKeys(PGPPublicKeyRing keyRing) { + Vector usableKeys = new Vector(); + Vector encryptKeys = getEncryptKeys(keyRing); + PGPPublicKey masterKey = null; + for (int i = 0; i < encryptKeys.size(); ++i) { + PGPPublicKey key = encryptKeys.get(i); + if (!isExpired(key)) { + if (key.isMasterKey()) { + masterKey = key; + } else { + usableKeys.add(key); + } + } + } + if (masterKey != null) { + usableKeys.add(masterKey); + } + return usableKeys; + } + + public static boolean isExpired(PGPPublicKey key) { + Date creationDate = getCreationDate(key); + Date expiryDate = getExpiryDate(key); + Date now = new Date(); + if (now.compareTo(creationDate) >= 0 && + (expiryDate == null || now.compareTo(expiryDate) <= 0)) { + return false; + } + return true; + } + + public static boolean isExpired(PGPSecretKey key) { + return isExpired(key.getPublicKey()); + } + + public static Vector getUsableSigningKeys(PGPSecretKeyRing keyRing) { + Vector usableKeys = new Vector(); + Vector signingKeys = getSigningKeys(keyRing); + PGPSecretKey masterKey = null; + for (int i = 0; i < signingKeys.size(); ++i) { + PGPSecretKey key = signingKeys.get(i); + if (key.isMasterKey()) { + masterKey = key; + } else { + usableKeys.add(key); + } + } + if (masterKey != null) { + usableKeys.add(masterKey); + } + return usableKeys; + } + + public static Date getExpiryDate(PGPPublicKey key) { + Date creationDate = getCreationDate(key); + if (key.getValidDays() == 0) { + // no expiry + return null; + } + Calendar calendar = GregorianCalendar.getInstance(); + calendar.setTime(creationDate); + calendar.add(Calendar.DATE, key.getValidDays()); + Date expiryDate = calendar.getTime(); + + return expiryDate; + } + + public static Date getExpiryDate(PGPSecretKey key) { + return getExpiryDate(key.getPublicKey()); + } + + public static PGPPublicKey getEncryptPublicKey(long masterKeyId) { + PGPPublicKeyRing keyRing = getPublicKeyRing(masterKeyId); + if (keyRing == null) { + return null; + } + Vector encryptKeys = getUsableEncryptKeys(keyRing); + if (encryptKeys.size() == 0) { + return null; + } + return encryptKeys.get(0); + } + + public static PGPSecretKey getSigningKey(long masterKeyId) { + PGPSecretKeyRing keyRing = getSecretKeyRing(masterKeyId); + if (keyRing == null) { + return null; + } + Vector signingKeys = getUsableSigningKeys(keyRing); + if (signingKeys.size() == 0) { + return null; + } + return signingKeys.get(0); + } + + public static String getMainUserId(PGPPublicKey key) { + for (String userId : new IterableIterator(key.getUserIDs())) { + return userId; + } + return null; + } + + public static String getMainUserId(PGPSecretKey key) { + for (String userId : new IterableIterator(key.getUserIDs())) { + return userId; + } + return null; + } + + public static String getMainUserIdSafe(Context context, PGPPublicKey key) { + String userId = getMainUserId(key); + if (userId == null) { + userId = context.getResources().getString(R.string.unknownUserId); + } + return userId; + } + + public static String getMainUserIdSafe(Context context, PGPSecretKey key) { + String userId = getMainUserId(key); + if (userId == null) { + userId = context.getResources().getString(R.string.unknownUserId); + } + return userId; + } + + public static boolean isEncryptionKey(PGPPublicKey key) { + if (!key.isEncryptionKey()) { + return false; + } + + if (key.getVersion() <= 3) { + // this must be true now + return key.isEncryptionKey(); + } + + // special cases + if (key.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT) { + return true; + } + + if (key.getAlgorithm() == PGPPublicKey.RSA_ENCRYPT) { + return true; + } + + for (PGPSignature sig : new IterableIterator(key.getSignatures())) { + if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { + continue; + } + PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + + if (hashed != null &&(hashed.getKeyFlags() & + (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) { + return true; + } + + PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); + + if (unhashed != null &&(unhashed.getKeyFlags() & + (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) { + return true; + } + } + return false; + } + + public static boolean isEncryptionKey(PGPSecretKey key) { + return isEncryptionKey(key.getPublicKey()); + } + + public static boolean isSigningKey(PGPPublicKey key) { + if (key.getVersion() <= 3) { + return true; + } + + // special case + if (key.getAlgorithm() == PGPPublicKey.RSA_SIGN) { + return true; + } + + for (PGPSignature sig : new IterableIterator(key.getSignatures())) { + if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { + continue; + } + PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + + if (hashed != null && (hashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) { + return true; + } + + PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); + + if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) { + return true; + } + } + + return false; + } + + public static boolean isSigningKey(PGPSecretKey key) { + return isSigningKey(key.getPublicKey()); + } + + public static String getAlgorithmInfo(PGPPublicKey key) { + return getAlgorithmInfo(key.getAlgorithm(), key.getBitStrength()); + } + + public static String getAlgorithmInfo(PGPSecretKey key) { + return getAlgorithmInfo(key.getPublicKey()); + } + + public static String getAlgorithmInfo(int algorithm, int keySize) { + String algorithmStr = null; + + switch (algorithm) { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + case PGPPublicKey.RSA_SIGN: { + algorithmStr = "RSA"; + break; + } + + case PGPPublicKey.DSA: { + algorithmStr = "DSA"; + break; + } + + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: { + algorithmStr = "ElGamal"; + break; + } + + default: { + algorithmStr = "???"; + break; + } + } + return algorithmStr + ", " + keySize + "bit"; + } + + public static void deleteKey(int keyRingId) { + mDatabase.deleteKeyRing(keyRingId); + } + + public static Object getKeyRing(int keyRingId) { + return mDatabase.getKeyRing(keyRingId); + } + + public static PGPSecretKeyRing getSecretKeyRing(long keyId) { + byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_secret, keyId); + if (data == null) { + return null; + } + try { + return new PGPSecretKeyRing(data); + } catch (IOException e) { + // no good way to handle this, return null + // TODO: some info? + } catch (PGPException e) { + // no good way to handle this, return null + // TODO: some info? + } + return null; + } + + public static PGPPublicKeyRing getPublicKeyRing(long keyId) { + byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_public, keyId); + if (data == null) { + return null; + } + try { + return new PGPPublicKeyRing(data); + } catch (IOException e) { + // no good way to handle this, return null + // TODO: some info? + } + return null; + } + + public static PGPSecretKey getSecretKey(long keyId) { + PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); + if (keyRing == null) { + return null; + } + return keyRing.getSecretKey(keyId); + } + + public static PGPPublicKey getPublicKey(long keyId) { + PGPPublicKeyRing keyRing = getPublicKeyRing(keyId); + if (keyRing == null) { + return null; + } + try { + return keyRing.getPublicKey(keyId); + } catch (PGPException e) { + return null; + } + } + + public static Vector getKeyRingIds(int type) { + SQLiteDatabase db = mDatabase.db(); + Vector keyIds = new Vector(); + Cursor c = db.query(KeyRings.TABLE_NAME, + new String[] { KeyRings._ID }, + KeyRings.TYPE + " = ?", new String[] { "" + type }, + null, null, null); + if (c != null && c.moveToFirst()) { + do { + keyIds.add(c.getInt(0)); + } while (c.moveToNext()); + } + + if (c != null) { + c.close(); + } + + return keyIds; + } + + public static String getMainUserId(long keyId, int type) { + SQLiteDatabase db = mDatabase.db(); + Cursor c = db.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ") " + + " INNER JOIN " + Keys.TABLE_NAME + " AS masterKey ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + "masterKey." + Keys.KEY_RING_ID + " AND " + + "masterKey." + Keys.IS_MASTER_KEY + " = '1') " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON (" + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " = " + + "masterKey." + Keys._ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0')", + new String[] { UserIds.USER_ID }, + Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " + + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", + new String[] { + "" + keyId, + "" + type, + }, + null, null, null); + String userId = ""; + if (c != null && c.moveToFirst()) { + do { + userId = c.getString(0); + } while (c.moveToNext()); + } + + if (c != null) { + c.close(); + } + + return userId; + } + + public static void encrypt(Context context, + InputData data, OutputStream outStream, + boolean armored, + long encryptionKeyIds[], long signatureKeyId, + String signaturePassPhrase, + ProgressDialogUpdater progress, + int symmetricAlgorithm, int hashAlgorithm, int compression, + String passPhrase) + throws IOException, GeneralException, PGPException, NoSuchProviderException, + NoSuchAlgorithmException, SignatureException { + Security.addProvider(new BouncyCastleProvider()); + + if (encryptionKeyIds == null) { + encryptionKeyIds = new long[0]; + } + + ArmoredOutputStream armorOut = null; + OutputStream out = null; + OutputStream encryptOut = null; + if (armored) { + armorOut = new ArmoredOutputStream(outStream); + armorOut.setHeader("Version", getFullVersion(context)); + out = armorOut; + } else { + out = outStream; + } + PGPSecretKey signingKey = null; + PGPSecretKeyRing signingKeyRing = null; + PGPPrivateKey signaturePrivateKey = null; + + if (encryptionKeyIds.length == 0 && passPhrase == null) { + throw new GeneralException(context.getString(R.string.error_noEncryptionKeysOrPassPhrase)); + } + + if (signatureKeyId != 0) { + signingKeyRing = getSecretKeyRing(signatureKeyId); + signingKey = getSigningKey(signatureKeyId); + if (signingKey == null) { + throw new GeneralException(context.getString(R.string.error_signatureFailed)); + } + + if (signaturePassPhrase == null) { + throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); + } + progress.setProgress(R.string.progress_extractingSignatureKey, 0, 100); + signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), + new BouncyCastleProvider()); + } + + PGPSignatureGenerator signatureGenerator = null; + progress.setProgress(R.string.progress_preparingStreams, 5, 100); + // encrypt and compress input file content + PGPEncryptedDataGenerator cPk = + new PGPEncryptedDataGenerator(symmetricAlgorithm, true, new SecureRandom(), + new BouncyCastleProvider()); + + if (encryptionKeyIds.length == 0) { + // symmetric encryption + cPk.addMethod(passPhrase.toCharArray()); + } + for (int i = 0; i < encryptionKeyIds.length; ++i) { + PGPPublicKey key = getEncryptPublicKey(encryptionKeyIds[i]); + if (key != null) { + cPk.addMethod(key); + } + } + encryptOut = cPk.open(out, new byte[1 << 16]); + + if (signatureKeyId != 0) { + progress.setProgress(R.string.progress_preparingSignature, 10, 100); + signatureGenerator = + new PGPSignatureGenerator(signingKey.getPublicKey().getAlgorithm(), + hashAlgorithm, + new BouncyCastleProvider()); + signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); + String userId = getMainUserId(getMasterKey(signingKeyRing)); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } + + PGPCompressedDataGenerator compressGen = null; + BCPGOutputStream bcpgOut = null; + if (compression == Id.choice.compression.none) { + bcpgOut = new BCPGOutputStream(encryptOut); + } else { + compressGen = new PGPCompressedDataGenerator(compression); + bcpgOut = new BCPGOutputStream(compressGen.open(encryptOut)); + } + if (signatureKeyId != 0) { + signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); + } + + PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); + // file name not needed, so empty string + OutputStream pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", + new Date(), new byte[1 << 16]); + + progress.setProgress(R.string.progress_encrypting, 20, 100); + long done = 0; + int n = 0; + byte[] buffer = new byte[1 << 16]; + InputStream in = data.getInputStream(); + while ((n = in.read(buffer)) > 0) { + pOut.write(buffer, 0, n); + if (signatureKeyId != 0) { + signatureGenerator.update(buffer, 0, n); + } + done += n; + if (data.getSize() != 0) { + progress.setProgress((int) (20 + (95 - 20) * done / data.getSize()), 100); + } + } + + literalGen.close(); + + if (signatureKeyId != 0) { + progress.setProgress(R.string.progress_generatingSignature, 95, 100); + signatureGenerator.generate().encode(pOut); + } + if (compressGen != null) { + compressGen.close(); + } + encryptOut.close(); + if (armored) { + armorOut.close(); + } + + progress.setProgress(R.string.progress_done, 100, 100); + } + + public static void signText(Context context, + InputData data, OutputStream outStream, + long signatureKeyId, String signaturePassPhrase, + int hashAlgorithm, + ProgressDialogUpdater progress) + throws GeneralException, PGPException, IOException, NoSuchAlgorithmException, + SignatureException { + Security.addProvider(new BouncyCastleProvider()); + + ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream); + armorOut.setHeader("Version", getFullVersion(context)); + + PGPSecretKey signingKey = null; + PGPSecretKeyRing signingKeyRing = null; + PGPPrivateKey signaturePrivateKey = null; + + if (signatureKeyId == 0) { + throw new GeneralException(context.getString(R.string.error_noSignatureKey)); + } + + signingKeyRing = getSecretKeyRing(signatureKeyId); + signingKey = getSigningKey(signatureKeyId); + if (signingKey == null) { + throw new GeneralException(context.getString(R.string.error_signatureFailed)); + } + + if (signaturePassPhrase == null) { + throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); + } + signaturePrivateKey = + signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), + new BouncyCastleProvider()); + + PGPSignatureGenerator signatureGenerator = null; + progress.setProgress(R.string.progress_preparingStreams, 0, 100); + + progress.setProgress(R.string.progress_preparingSignature, 30, 100); + signatureGenerator = + new PGPSignatureGenerator(signingKey.getPublicKey().getAlgorithm(), + hashAlgorithm, + new BouncyCastleProvider()); + signatureGenerator.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); + String userId = getMainUserId(getMasterKey(signingKeyRing)); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + + progress.setProgress(R.string.progress_signing, 40, 100); + + armorOut.beginClearText(hashAlgorithm); + + ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); + InputStream inStream = data.getInputStream(); + int lookAhead = readInputLine(lineOut, inStream); + + processLine(armorOut, signatureGenerator, lineOut.toByteArray()); + + if (lookAhead != -1) { + do { + lookAhead = readInputLine(lineOut, lookAhead, inStream); + + signatureGenerator.update((byte)'\r'); + signatureGenerator.update((byte)'\n'); + + processLine(armorOut, signatureGenerator, lineOut.toByteArray()); + } + while (lookAhead != -1); + } + + armorOut.endClearText(); + + BCPGOutputStream bOut = new BCPGOutputStream(armorOut); + signatureGenerator.generate().encode(bOut); + armorOut.close(); + + progress.setProgress(R.string.progress_done, 100, 100); + } + + public static long getDecryptionKeyId(Context context, InputData data) + throws GeneralException, NoAsymmetricEncryptionException, IOException { + InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + Object o = pgpF.nextObject(); + + // the first object might be a PGP marker packet. + if (o instanceof PGPEncryptedDataList) { + enc = (PGPEncryptedDataList) o; + } else { + enc = (PGPEncryptedDataList) pgpF.nextObject(); + } + + if (enc == null) { + throw new GeneralException(context.getString(R.string.error_invalidData)); + } + + // TODO: currently we always only look at the first known key + // find the secret key + PGPSecretKey secretKey = null; + Iterator it = enc.getEncryptedDataObjects(); + boolean gotAsymmetricEncryption = false; + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPublicKeyEncryptedData) { + gotAsymmetricEncryption = true; + PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj; + secretKey = getSecretKey(pbe.getKeyID()); + if (secretKey != null) { + break; + } + } + } + + if (!gotAsymmetricEncryption) { + throw new NoAsymmetricEncryptionException(); + } + + if (secretKey == null) { + return Id.key.none; + } + + return secretKey.getKeyID(); + } + + public static boolean hasSymmetricEncryption(Context context, InputData data) + throws GeneralException, IOException { + InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + Object o = pgpF.nextObject(); + + // the first object might be a PGP marker packet. + if (o instanceof PGPEncryptedDataList) { + enc = (PGPEncryptedDataList) o; + } else { + enc = (PGPEncryptedDataList) pgpF.nextObject(); + } + + if (enc == null) { + throw new GeneralException(context.getString(R.string.error_invalidData)); + } + + Iterator it = enc.getEncryptedDataObjects(); + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPBEEncryptedData) { + return true; + } + } + + return false; + } + + public static Bundle decrypt(Context context, + InputData data, OutputStream outStream, + String passPhrase, ProgressDialogUpdater progress, + boolean assumeSymmetric) + throws IOException, GeneralException, PGPException, SignatureException { + if (passPhrase == null) { + passPhrase = ""; + } + Bundle returnData = new Bundle(); + InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + Object o = pgpF.nextObject(); + long signatureKeyId = 0; + + int currentProgress = 0; + progress.setProgress(R.string.progress_readingData, currentProgress, 100); + + if (o instanceof PGPEncryptedDataList) { + enc = (PGPEncryptedDataList) o; + } else { + enc = (PGPEncryptedDataList) pgpF.nextObject(); + } + + if (enc == null) { + throw new GeneralException(context.getString(R.string.error_invalidData)); + } + + InputStream clear = null; + PGPEncryptedData encryptedData = null; + + currentProgress += 5; + + // TODO: currently we always only look at the first known key or symmetric encryption, + // there might be more... + if (assumeSymmetric) { + PGPPBEEncryptedData pbe = null; + Iterator it = enc.getEncryptedDataObjects(); + // find secret key + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPBEEncryptedData) { + pbe = (PGPPBEEncryptedData) obj; + break; + } + } + + if (pbe == null) { + throw new GeneralException(context.getString(R.string.error_noSymmetricEncryptionPacket)); + } + + progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100); + clear = pbe.getDataStream(passPhrase.toCharArray(), new BouncyCastleProvider()); + encryptedData = pbe; + currentProgress += 5; + } else { + progress.setProgress(R.string.progress_findingKey, currentProgress, 100); + PGPPublicKeyEncryptedData pbe = null; + PGPSecretKey secretKey = null; + Iterator it = enc.getEncryptedDataObjects(); + // find secret key + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPublicKeyEncryptedData) { + PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; + secretKey = getSecretKey(encData.getKeyID()); + if (secretKey != null) { + pbe = encData; + break; + } + } + } + + if (secretKey == null) { + throw new GeneralException(context.getString(R.string.error_noSecretKeyFound)); + } + + currentProgress += 5; + progress.setProgress(R.string.progress_extractingKey, currentProgress, 100); + PGPPrivateKey privateKey = null; + try { + privateKey = secretKey.extractPrivateKey(passPhrase.toCharArray(), + new BouncyCastleProvider()); + } catch (PGPException e) { + throw new PGPException(context.getString(R.string.error_wrongPassPhrase)); + } + currentProgress += 5; + progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100); + clear = pbe.getDataStream(privateKey, new BouncyCastleProvider()); + encryptedData = pbe; + currentProgress += 5; + } + + PGPObjectFactory plainFact = new PGPObjectFactory(clear); + Object dataChunk = plainFact.nextObject(); + PGPOnePassSignature signature = null; + PGPPublicKey signatureKey = null; + int signatureIndex = -1; + + if (dataChunk instanceof PGPCompressedData) { + progress.setProgress(R.string.progress_decompressingData, currentProgress, 100); + PGPObjectFactory fact = + new PGPObjectFactory(((PGPCompressedData) dataChunk).getDataStream()); + dataChunk = fact.nextObject(); + plainFact = fact; + currentProgress += 10; + } + + if (dataChunk instanceof PGPOnePassSignatureList) { + progress.setProgress(R.string.progress_processingSignature, currentProgress, 100); + returnData.putBoolean(EXTRA_SIGNATURE, true); + PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; + for (int i = 0; i < sigList.size(); ++i) { + signature = sigList.get(i); + signatureKey = getPublicKey(signature.getKeyID()); + if (signatureKeyId == 0) { + signatureKeyId = signature.getKeyID(); + } + if (signatureKey == null) { + signature = null; + } else { + signatureIndex = i; + signatureKeyId = signature.getKeyID(); + String userId = null; + PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); + if (sigKeyRing != null) { + userId = getMainUserId(getMasterKey(sigKeyRing)); + } + returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); + break; + } + } + + returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); + + if (signature != null) { + signature.initVerify(signatureKey, new BouncyCastleProvider()); + } else { + returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true); + } + + dataChunk = plainFact.nextObject(); + currentProgress += 10; + } + + if (dataChunk instanceof PGPLiteralData) { + progress.setProgress(R.string.progress_decrypting, currentProgress, 100); + PGPLiteralData literalData = (PGPLiteralData) dataChunk; + OutputStream out = outStream; + + byte[] buffer = new byte[1 << 16]; + InputStream dataIn = literalData.getInputStream(); + + int startProgress = currentProgress; + int endProgress = 100; + if (signature != null) { + endProgress = 90; + } else if (encryptedData.isIntegrityProtected()) { + endProgress = 95; + } + int n = 0; + int done = 0; + long startPos = data.getStreamPosition(); + while ((n = dataIn.read(buffer)) > 0) { + out.write(buffer, 0, n); + done += n; + if (signature != null) { + try { + signature.update(buffer, 0, n); + } catch (SignatureException e) { + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, false); + signature = null; + } + } + // unknown size, but try to at least have a moving, slowing down progress bar + currentProgress = startProgress + (endProgress - startProgress) * done / (done + 100000); + if (data.getSize() - startPos == 0) { + currentProgress = endProgress; + } else { + currentProgress = (int)(startProgress + (endProgress - startProgress) * + (data.getStreamPosition() - startPos) / (data.getSize() - startPos)); + } + progress.setProgress(currentProgress, 100); + } + + if (signature != null) { + progress.setProgress(R.string.progress_verifyingSignature, 90, 100); + PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); + PGPSignature messageSignature = (PGPSignature) signatureList.get(signatureIndex); + if (signature.verify(messageSignature)) { + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, true); + } else { + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, false); + } + } + } + + // TODO: add integrity somewhere + if (encryptedData.isIntegrityProtected()) { + progress.setProgress(R.string.progress_verifyingIntegrity, 95, 100); + if (encryptedData.verify()) { + // passed + } else { + // failed + } + } else { + // no integrity check + } + + progress.setProgress(R.string.progress_done, 100, 100); + return returnData; + } + + public static Bundle verifyText(Context context, + InputData data, OutputStream outStream, + ProgressDialogUpdater progress) + throws IOException, GeneralException, PGPException, SignatureException { + Bundle returnData = new Bundle(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ArmoredInputStream aIn = new ArmoredInputStream(data.getInputStream()); + + progress.setProgress(R.string.progress_done, 0, 100); + + // mostly taken from ClearSignedFileProcessor + ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); + int lookAhead = readInputLine(lineOut, aIn); + byte[] lineSep = getLineSeparator(); + + byte[] line = lineOut.toByteArray(); + out.write(line, 0, getLengthWithoutSeparator(line)); + out.write(lineSep); + + while (lookAhead != -1 && aIn.isClearText()) { + lookAhead = readInputLine(lineOut, lookAhead, aIn); + line = lineOut.toByteArray(); + out.write(line, 0, getLengthWithoutSeparator(line)); + out.write(lineSep); + } + + out.close(); + + byte[] clearText = out.toByteArray(); + outStream.write(clearText); + + returnData.putBoolean(EXTRA_SIGNATURE, true); + + progress.setProgress(R.string.progress_processingSignature, 60, 100); + PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); + + PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); + if (sigList == null) { + throw new GeneralException(context.getString(R.string.error_corruptData)); + } + PGPSignature signature = null; + long signatureKeyId = 0; + PGPPublicKey signatureKey = null; + for (int i = 0; i < sigList.size(); ++i) { + signature = sigList.get(i); + signatureKey = getPublicKey(signature.getKeyID()); + if (signatureKeyId == 0) { + signatureKeyId = signature.getKeyID(); + } + if (signatureKey == null) { + signature = null; + } else { + signatureKeyId = signature.getKeyID(); + String userId = null; + PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); + if (sigKeyRing != null) { + userId = getMainUserId(getMasterKey(sigKeyRing)); + } + returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); + break; + } + } + + returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); + + if (signature == null) { + returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true); + progress.setProgress(R.string.progress_done, 100, 100); + return returnData; + } + + signature.initVerify(signatureKey, new BouncyCastleProvider()); + + InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText)); + + lookAhead = readInputLine(lineOut, sigIn); + + processLine(signature, lineOut.toByteArray()); + + if (lookAhead != -1) { + do { + lookAhead = readInputLine(lineOut, lookAhead, sigIn); + + signature.update((byte)'\r'); + signature.update((byte)'\n'); + + processLine(signature, lineOut.toByteArray()); + } + while (lookAhead != -1); + } + + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, signature.verify()); + + progress.setProgress(R.string.progress_done, 100, 100); + return returnData; + } + + public static int getStreamContent(Context context, InputStream inStream) + throws IOException { + InputStream in = PGPUtil.getDecoderStream(inStream); + PGPObjectFactory pgpF = new PGPObjectFactory(in); + Object object = pgpF.nextObject(); + while (object != null) { + if (object instanceof PGPPublicKeyRing || + object instanceof PGPSecretKeyRing) { + return Id.content.keys; + } else if (object instanceof PGPEncryptedDataList) { + return Id.content.encrypted_data; + } + object = pgpF.nextObject(); + } + + return Id.content.unknown; + } + + // taken from ClearSignedFileProcessor in BC + private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) + throws IOException { + bOut.reset(); + + int lookAhead = -1; + int ch; + + while ((ch = fIn.read()) >= 0) { + bOut.write(ch); + if (ch == '\r' || ch == '\n') { + lookAhead = readPassedEOL(bOut, ch, fIn); + break; + } + } + + return lookAhead; + } + + private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) + throws IOException { + bOut.reset(); + + int ch = lookAhead; + + do { + bOut.write(ch); + if (ch == '\r' || ch == '\n') { + lookAhead = readPassedEOL(bOut, ch, fIn); + break; + } + } + while ((ch = fIn.read()) >= 0); + + if (ch < 0) { + lookAhead = -1; + } + + return lookAhead; + } + + private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) + throws IOException { + int lookAhead = fIn.read(); + + if (lastCh == '\r' && lookAhead == '\n') { + bOut.write(lookAhead); + lookAhead = fIn.read(); + } + + return lookAhead; + } + + private static void processLine(PGPSignature sig, byte[] line) + throws SignatureException, IOException { + int length = getLengthWithoutWhiteSpace(line); + if (length > 0) { + sig.update(line, 0, length); + } + } + + private static void processLine(OutputStream aOut, PGPSignatureGenerator sGen, byte[] line) + throws SignatureException, IOException { + int length = getLengthWithoutWhiteSpace(line); + if (length > 0) { + sGen.update(line, 0, length); + } + + aOut.write(line, 0, line.length); + } + + private static int getLengthWithoutSeparator(byte[] line) { + int end = line.length - 1; + + while (end >= 0 && isLineEnding(line[end])) { + end--; + } + + return end + 1; + } + + private static boolean isLineEnding(byte b) { + return b == '\r' || b == '\n'; + } + + private static int getLengthWithoutWhiteSpace(byte[] line) { + int end = line.length - 1; + + while (end >= 0 && isWhiteSpace(line[end])) { + end--; + } + + return end + 1; + } + + private static boolean isWhiteSpace(byte b) { + return b == '\r' || b == '\n' || b == '\t' || b == ' '; + } + + private static byte[] getLineSeparator() { + String nl = System.getProperty("line.separator"); + byte[] nlBytes = new byte[nl.length()]; + + for (int i = 0; i != nlBytes.length; i++) { + nlBytes[i] = (byte)nl.charAt(i); + } + + return nlBytes; + } + + public static String getVersion(Context context) { + if (VERSION != null) { + return VERSION; + } + try { + PackageInfo pi = context.getPackageManager().getPackageInfo(mApgPackageName, 0); + VERSION = pi.versionName; + return VERSION; + } catch (NameNotFoundException e) { + // unpossible! + return "0.0.0"; + } + } + + public static String getFullVersion(Context context) { + return "APG v" + getVersion(context); + } + + public static String generateRandomString(int length) { + SecureRandom random = new SecureRandom(); + /* + try { + random = SecureRandom.getInstance("SHA1PRNG", new BouncyCastleProvider()); + } catch (NoSuchAlgorithmException e) { + // TODO: need to handle this case somehow + return null; + }*/ + byte bytes[] = new byte[length]; + random.nextBytes(bytes); + String result = ""; + for (int i = 0; i < length; ++i) { + int v = ((int)bytes[i] + 256) % 64; + if (v < 10) { + result += (char)((int)'0' + v); + } else if (v < 36) { + result += (char)((int)'A' + v - 10); + } else if (v < 62) { + result += (char)((int)'a' + v - 36); + } else if (v == 62) { + result += '_'; + } else if (v == 63) { + result += '.'; + } + } + return result; + } + + static long getLengthOfStream(InputStream in) throws IOException { + long size = 0; + long n = 0; + byte dummy[] = new byte[0x10000]; + while ((n = in.read(dummy)) > 0) { + size += n; + } + return size; + } +} diff --git a/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java b/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java index 01cd2de25..7c582ef0c 100644 --- a/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java +++ b/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java @@ -1,112 +1,112 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import org.bouncycastle2.jce.provider.BouncyCastleProvider; -import org.bouncycastle2.openpgp.PGPException; -import org.bouncycastle2.openpgp.PGPSecretKey; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.EditText; -import android.widget.Toast; - -public class AskForSecretKeyPassPhrase { - public static interface PassPhraseCallbackInterface { - void passPhraseCallback(long keyId, String passPhrase); - } - - public static Dialog createDialog(Activity context, long secretKeyId, - PassPhraseCallbackInterface callback) { - AlertDialog.Builder alert = new AlertDialog.Builder(context); - - alert.setTitle(R.string.title_authentification); - - final PGPSecretKey secretKey; - final Activity activity = context; - - if (secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none) { - secretKey = null; - alert.setMessage(context.getString(R.string.passPhraseForSymmetricEncryption)); - } else { - secretKey = Apg.getMasterKey(Apg.getSecretKeyRing(secretKeyId)); - if (secretKey == null) { - alert.setTitle(R.string.title_keyNotFound); - alert.setMessage(context.getString(R.string.keyNotFound, secretKeyId)); - alert.setPositiveButton(android.R.string.ok, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - activity.removeDialog(Id.dialog.pass_phrase); - } - }); - alert.setCancelable(false); - return alert.create(); - } - String userId = Apg.getMainUserIdSafe(context, secretKey); - alert.setMessage(context.getString(R.string.passPhraseFor, userId)); - } - - LayoutInflater inflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View view = inflater.inflate(R.layout.pass_phrase, null); - final EditText input = (EditText) view.findViewById(R.id.passPhrase); - final EditText inputNotUsed = (EditText) view.findViewById(R.id.passPhraseAgain); - inputNotUsed.setVisibility(View.GONE); - - alert.setView(view); - - final PassPhraseCallbackInterface cb = callback; - alert.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - activity.removeDialog(Id.dialog.pass_phrase); - String passPhrase = "" + input.getText(); - long keyId; - if (secretKey != null) { - try { - secretKey.extractPrivateKey(passPhrase.toCharArray(), - new BouncyCastleProvider()); - } catch (PGPException e) { - Toast.makeText(activity, - R.string.wrongPassPhrase, - Toast.LENGTH_SHORT).show(); - return; - } - keyId = secretKey.getKeyID(); - } else { - keyId = Id.key.symmetric; - } - cb.passPhraseCallback(keyId, passPhrase); - } - }); - - alert.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - activity.removeDialog(Id.dialog.pass_phrase); - } - }); - - return alert.create(); - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import org.bouncycastle2.jce.provider.BouncyCastleProvider; +import org.bouncycastle2.openpgp.PGPException; +import org.bouncycastle2.openpgp.PGPSecretKey; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.Toast; + +public class AskForSecretKeyPassPhrase { + public static interface PassPhraseCallbackInterface { + void passPhraseCallback(long keyId, String passPhrase); + } + + public static Dialog createDialog(Activity context, long secretKeyId, + PassPhraseCallbackInterface callback) { + AlertDialog.Builder alert = new AlertDialog.Builder(context); + + alert.setTitle(R.string.title_authentification); + + final PGPSecretKey secretKey; + final Activity activity = context; + + if (secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none) { + secretKey = null; + alert.setMessage(context.getString(R.string.passPhraseForSymmetricEncryption)); + } else { + secretKey = Apg.getMasterKey(Apg.getSecretKeyRing(secretKeyId)); + if (secretKey == null) { + alert.setTitle(R.string.title_keyNotFound); + alert.setMessage(context.getString(R.string.keyNotFound, secretKeyId)); + alert.setPositiveButton(android.R.string.ok, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + activity.removeDialog(Id.dialog.pass_phrase); + } + }); + alert.setCancelable(false); + return alert.create(); + } + String userId = Apg.getMainUserIdSafe(context, secretKey); + alert.setMessage(context.getString(R.string.passPhraseFor, userId)); + } + + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.pass_phrase, null); + final EditText input = (EditText) view.findViewById(R.id.passPhrase); + final EditText inputNotUsed = (EditText) view.findViewById(R.id.passPhraseAgain); + inputNotUsed.setVisibility(View.GONE); + + alert.setView(view); + + final PassPhraseCallbackInterface cb = callback; + alert.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + activity.removeDialog(Id.dialog.pass_phrase); + String passPhrase = "" + input.getText(); + long keyId; + if (secretKey != null) { + try { + secretKey.extractPrivateKey(passPhrase.toCharArray(), + new BouncyCastleProvider()); + } catch (PGPException e) { + Toast.makeText(activity, + R.string.wrongPassPhrase, + Toast.LENGTH_SHORT).show(); + return; + } + keyId = secretKey.getKeyID(); + } else { + keyId = Id.key.symmetric; + } + cb.passPhraseCallback(keyId, passPhrase); + } + }); + + alert.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + activity.removeDialog(Id.dialog.pass_phrase); + } + }); + + return alert.create(); + } +} diff --git a/src/org/thialfihar/android/apg/BaseActivity.java b/src/org/thialfihar/android/apg/BaseActivity.java index 7fd8752b7..17fe3b0c8 100644 --- a/src/org/thialfihar/android/apg/BaseActivity.java +++ b/src/org/thialfihar/android/apg/BaseActivity.java @@ -1,381 +1,381 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import java.io.File; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.Message; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; - -public class BaseActivity extends Activity - implements Runnable, ProgressDialogUpdater, - AskForSecretKeyPassPhrase.PassPhraseCallbackInterface { - - private ProgressDialog mProgressDialog = null; - private Thread mRunningThread = null; - - private long mSecretKeyId = 0; - private String mDeleteFile = null; - - protected Preferences mPreferences; - - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - handlerCallback(msg); - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mPreferences = Preferences.getPreferences(this); - - Apg.initialize(this); - - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - File dir = new File(Constants.path.app_dir); - if (!dir.exists() && !dir.mkdirs()) { - // ignore this for now, it's not crucial - // that the directory doesn't exist at this point - } - } - - startCacheService(this, mPreferences); - } - - public static void startCacheService(Activity activity, Preferences preferences) { - Intent intent = new Intent(activity, Service.class); - intent.putExtra(Service.EXTRA_TTL, preferences.getPassPhraseCacheTtl()); - activity.startService(intent); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences) - .setIcon(android.R.drawable.ic_menu_preferences); - menu.add(0, Id.menu.option.about, 1, R.string.menu_about) - .setIcon(android.R.drawable.ic_menu_info_details); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case Id.menu.option.about: { - showDialog(Id.dialog.about); - return true; - } - - case Id.menu.option.preferences: { - startActivity(new Intent(this, PreferencesActivity.class)); - return true; - } - - case Id.menu.option.search: { - startSearch("", false, null, false); - return true; - } - - default: { - break; - } - } - return false; - } - - @Override - protected Dialog onCreateDialog(int id) { - // in case it is a progress dialog - mProgressDialog = new ProgressDialog(this); - mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - mProgressDialog.setCancelable(false); - switch (id) { - case Id.dialog.encrypting: { - mProgressDialog.setMessage(this.getString(R.string.progress_initializing)); - return mProgressDialog; - } - - case Id.dialog.decrypting: { - mProgressDialog.setMessage(this.getString(R.string.progress_initializing)); - return mProgressDialog; - } - - case Id.dialog.saving: { - mProgressDialog.setMessage(this.getString(R.string.progress_saving)); - return mProgressDialog; - } - - case Id.dialog.importing: { - mProgressDialog.setMessage(this.getString(R.string.progress_importing)); - return mProgressDialog; - } - - case Id.dialog.exporting: { - mProgressDialog.setMessage(this.getString(R.string.progress_exporting)); - return mProgressDialog; - } - - default: { - break; - } - } - mProgressDialog = null; - - switch (id) { - case Id.dialog.about: { - AlertDialog.Builder alert = new AlertDialog.Builder(this); - - alert.setTitle("About " + Apg.getFullVersion(this)); - - LayoutInflater inflater = - (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View layout = inflater.inflate(R.layout.info, null); - TextView message = (TextView) layout.findViewById(R.id.message); - message.setText("This is an attempt to bring OpenPGP to Android. " + - "It is far from complete, but more features are planned (see website).\n\n" + - "Feel free to send bug reports, suggestions, feature requests, feedback, " + - "photographs.\n\n" + - "mail: thi@thialfihar.org\n" + - "site: http://apg.thialfihar.org\n\n" + - "This software is provided \"as is\", without warranty of any kind."); - alert.setView(layout); - - alert.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - BaseActivity.this.removeDialog(Id.dialog.about); - } - }); - - return alert.create(); - } - - case Id.dialog.pass_phrase: { - return AskForSecretKeyPassPhrase.createDialog(this, getSecretKeyId(), this); - } - - case Id.dialog.pass_phrases_do_not_match: { - AlertDialog.Builder alert = new AlertDialog.Builder(this); - - alert.setIcon(android.R.drawable.ic_dialog_alert); - alert.setTitle(R.string.error); - alert.setMessage(R.string.passPhrasesDoNotMatch); - - alert.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.pass_phrases_do_not_match); - } - }); - alert.setCancelable(false); - - return alert.create(); - } - - case Id.dialog.no_pass_phrase: { - AlertDialog.Builder alert = new AlertDialog.Builder(this); - - alert.setIcon(android.R.drawable.ic_dialog_alert); - alert.setTitle(R.string.error); - alert.setMessage(R.string.passPhraseMustNotBeEmpty); - - alert.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.no_pass_phrase); - } - }); - alert.setCancelable(false); - - return alert.create(); - } - - case Id.dialog.delete_file: { - AlertDialog.Builder alert = new AlertDialog.Builder(this); - - alert.setIcon(android.R.drawable.ic_dialog_alert); - alert.setTitle(R.string.warning); - alert.setMessage(this.getString(R.string.fileDeleteConfirmation, getDeleteFile())); - - alert.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.delete_file); - File file = new File(getDeleteFile()); - String msg = ""; - if (file.delete()) { - msg = BaseActivity.this.getString( - R.string.fileDeleteSuccessful); - } else { - msg = BaseActivity.this.getString( - R.string.errorMessage, - BaseActivity.this.getString( - R.string.error_fileDeleteFailed, file)); - } - Toast.makeText(BaseActivity.this, - msg, Toast.LENGTH_SHORT).show(); - } - }); - alert.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.delete_file); - } - }); - alert.setCancelable(true); - - return alert.create(); - } - - default: { - break; - } - } - - return super.onCreateDialog(id); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.secret_keys: { - if (resultCode == RESULT_OK) { - Bundle bundle = data.getExtras(); - setSecretKeyId(bundle.getLong(Apg.EXTRA_KEY_ID)); - } else { - setSecretKeyId(Id.key.none); - } - break; - } - - default: { - break; - } - } - - super.onActivityResult(requestCode, resultCode, data); - } - - public void setProgress(int resourceId, int progress, int max) { - setProgress(getString(resourceId), progress, max); - } - - public void setProgress(int progress, int max) { - Message msg = new Message(); - Bundle data = new Bundle(); - data.putInt(Apg.EXTRA_STATUS, Id.message.progress_update); - data.putInt(Apg.EXTRA_PROGRESS, progress); - data.putInt(Apg.EXTRA_MAX, max); - msg.setData(data); - mHandler.sendMessage(msg); - } - - public void setProgress(String message, int progress, int max) { - Message msg = new Message(); - Bundle data = new Bundle(); - data.putInt(Apg.EXTRA_STATUS, Id.message.progress_update); - data.putString(Apg.EXTRA_MESSAGE, message); - data.putInt(Apg.EXTRA_PROGRESS, progress); - data.putInt(Apg.EXTRA_MAX, max); - msg.setData(data); - mHandler.sendMessage(msg); - } - - public void handlerCallback(Message msg) { - Bundle data = msg.getData(); - if (data == null) { - return; - } - - int type = data.getInt(Apg.EXTRA_STATUS); - switch (type) { - case Id.message.progress_update: { - String message = data.getString(Apg.EXTRA_MESSAGE); - if (mProgressDialog != null) { - if (message != null) { - mProgressDialog.setMessage(message); - } - mProgressDialog.setMax(data.getInt(Apg.EXTRA_MAX)); - mProgressDialog.setProgress(data.getInt(Apg.EXTRA_PROGRESS)); - } - break; - } - - case Id.message.import_done: // intentionall no break - case Id.message.export_done: // intentionall no break - case Id.message.done: { - mProgressDialog = null; - doneCallback(msg); - break; - } - } - } - - public void doneCallback(Message msg) { - - } - - public void passPhraseCallback(long keyId, String passPhrase) { - Apg.setCachedPassPhrase(keyId, passPhrase); - } - - public void sendMessage(Message msg) { - mHandler.sendMessage(msg); - } - - public void startThread() { - mRunningThread = new Thread(this); - mRunningThread.start(); - } - - public void run() { - - } - - public void setSecretKeyId(long id) { - mSecretKeyId = id; - } - - public long getSecretKeyId() { - return mSecretKeyId; - } - - protected void setDeleteFile(String deleteFile) { - mDeleteFile = deleteFile; - } - - protected String getDeleteFile() { - return mDeleteFile; - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import java.io.File; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +public class BaseActivity extends Activity + implements Runnable, ProgressDialogUpdater, + AskForSecretKeyPassPhrase.PassPhraseCallbackInterface { + + private ProgressDialog mProgressDialog = null; + private Thread mRunningThread = null; + + private long mSecretKeyId = 0; + private String mDeleteFile = null; + + protected Preferences mPreferences; + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + handlerCallback(msg); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mPreferences = Preferences.getPreferences(this); + + Apg.initialize(this); + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + File dir = new File(Constants.path.app_dir); + if (!dir.exists() && !dir.mkdirs()) { + // ignore this for now, it's not crucial + // that the directory doesn't exist at this point + } + } + + startCacheService(this, mPreferences); + } + + public static void startCacheService(Activity activity, Preferences preferences) { + Intent intent = new Intent(activity, Service.class); + intent.putExtra(Service.EXTRA_TTL, preferences.getPassPhraseCacheTtl()); + activity.startService(intent); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences) + .setIcon(android.R.drawable.ic_menu_preferences); + menu.add(0, Id.menu.option.about, 1, R.string.menu_about) + .setIcon(android.R.drawable.ic_menu_info_details); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Id.menu.option.about: { + showDialog(Id.dialog.about); + return true; + } + + case Id.menu.option.preferences: { + startActivity(new Intent(this, PreferencesActivity.class)); + return true; + } + + case Id.menu.option.search: { + startSearch("", false, null, false); + return true; + } + + default: { + break; + } + } + return false; + } + + @Override + protected Dialog onCreateDialog(int id) { + // in case it is a progress dialog + mProgressDialog = new ProgressDialog(this); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + mProgressDialog.setCancelable(false); + switch (id) { + case Id.dialog.encrypting: { + mProgressDialog.setMessage(this.getString(R.string.progress_initializing)); + return mProgressDialog; + } + + case Id.dialog.decrypting: { + mProgressDialog.setMessage(this.getString(R.string.progress_initializing)); + return mProgressDialog; + } + + case Id.dialog.saving: { + mProgressDialog.setMessage(this.getString(R.string.progress_saving)); + return mProgressDialog; + } + + case Id.dialog.importing: { + mProgressDialog.setMessage(this.getString(R.string.progress_importing)); + return mProgressDialog; + } + + case Id.dialog.exporting: { + mProgressDialog.setMessage(this.getString(R.string.progress_exporting)); + return mProgressDialog; + } + + default: { + break; + } + } + mProgressDialog = null; + + switch (id) { + case Id.dialog.about: { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + + alert.setTitle("About " + Apg.getFullVersion(this)); + + LayoutInflater inflater = + (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View layout = inflater.inflate(R.layout.info, null); + TextView message = (TextView) layout.findViewById(R.id.message); + message.setText("This is an attempt to bring OpenPGP to Android. " + + "It is far from complete, but more features are planned (see website).\n\n" + + "Feel free to send bug reports, suggestions, feature requests, feedback, " + + "photographs.\n\n" + + "mail: thi@thialfihar.org\n" + + "site: http://apg.thialfihar.org\n\n" + + "This software is provided \"as is\", without warranty of any kind."); + alert.setView(layout); + + alert.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + BaseActivity.this.removeDialog(Id.dialog.about); + } + }); + + return alert.create(); + } + + case Id.dialog.pass_phrase: { + return AskForSecretKeyPassPhrase.createDialog(this, getSecretKeyId(), this); + } + + case Id.dialog.pass_phrases_do_not_match: { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.setTitle(R.string.error); + alert.setMessage(R.string.passPhrasesDoNotMatch); + + alert.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.pass_phrases_do_not_match); + } + }); + alert.setCancelable(false); + + return alert.create(); + } + + case Id.dialog.no_pass_phrase: { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.setTitle(R.string.error); + alert.setMessage(R.string.passPhraseMustNotBeEmpty); + + alert.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.no_pass_phrase); + } + }); + alert.setCancelable(false); + + return alert.create(); + } + + case Id.dialog.delete_file: { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.setTitle(R.string.warning); + alert.setMessage(this.getString(R.string.fileDeleteConfirmation, getDeleteFile())); + + alert.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.delete_file); + File file = new File(getDeleteFile()); + String msg = ""; + if (file.delete()) { + msg = BaseActivity.this.getString( + R.string.fileDeleteSuccessful); + } else { + msg = BaseActivity.this.getString( + R.string.errorMessage, + BaseActivity.this.getString( + R.string.error_fileDeleteFailed, file)); + } + Toast.makeText(BaseActivity.this, + msg, Toast.LENGTH_SHORT).show(); + } + }); + alert.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.delete_file); + } + }); + alert.setCancelable(true); + + return alert.create(); + } + + default: { + break; + } + } + + return super.onCreateDialog(id); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.secret_keys: { + if (resultCode == RESULT_OK) { + Bundle bundle = data.getExtras(); + setSecretKeyId(bundle.getLong(Apg.EXTRA_KEY_ID)); + } else { + setSecretKeyId(Id.key.none); + } + break; + } + + default: { + break; + } + } + + super.onActivityResult(requestCode, resultCode, data); + } + + public void setProgress(int resourceId, int progress, int max) { + setProgress(getString(resourceId), progress, max); + } + + public void setProgress(int progress, int max) { + Message msg = new Message(); + Bundle data = new Bundle(); + data.putInt(Apg.EXTRA_STATUS, Id.message.progress_update); + data.putInt(Apg.EXTRA_PROGRESS, progress); + data.putInt(Apg.EXTRA_MAX, max); + msg.setData(data); + mHandler.sendMessage(msg); + } + + public void setProgress(String message, int progress, int max) { + Message msg = new Message(); + Bundle data = new Bundle(); + data.putInt(Apg.EXTRA_STATUS, Id.message.progress_update); + data.putString(Apg.EXTRA_MESSAGE, message); + data.putInt(Apg.EXTRA_PROGRESS, progress); + data.putInt(Apg.EXTRA_MAX, max); + msg.setData(data); + mHandler.sendMessage(msg); + } + + public void handlerCallback(Message msg) { + Bundle data = msg.getData(); + if (data == null) { + return; + } + + int type = data.getInt(Apg.EXTRA_STATUS); + switch (type) { + case Id.message.progress_update: { + String message = data.getString(Apg.EXTRA_MESSAGE); + if (mProgressDialog != null) { + if (message != null) { + mProgressDialog.setMessage(message); + } + mProgressDialog.setMax(data.getInt(Apg.EXTRA_MAX)); + mProgressDialog.setProgress(data.getInt(Apg.EXTRA_PROGRESS)); + } + break; + } + + case Id.message.import_done: // intentionall no break + case Id.message.export_done: // intentionall no break + case Id.message.done: { + mProgressDialog = null; + doneCallback(msg); + break; + } + } + } + + public void doneCallback(Message msg) { + + } + + public void passPhraseCallback(long keyId, String passPhrase) { + Apg.setCachedPassPhrase(keyId, passPhrase); + } + + public void sendMessage(Message msg) { + mHandler.sendMessage(msg); + } + + public void startThread() { + mRunningThread = new Thread(this); + mRunningThread.start(); + } + + public void run() { + + } + + public void setSecretKeyId(long id) { + mSecretKeyId = id; + } + + public long getSecretKeyId() { + return mSecretKeyId; + } + + protected void setDeleteFile(String deleteFile) { + mDeleteFile = deleteFile; + } + + protected String getDeleteFile() { + return mDeleteFile; + } +} diff --git a/src/org/thialfihar/android/apg/CachedPassPhrase.java b/src/org/thialfihar/android/apg/CachedPassPhrase.java index 74248aee3..92ef708c2 100644 --- a/src/org/thialfihar/android/apg/CachedPassPhrase.java +++ b/src/org/thialfihar/android/apg/CachedPassPhrase.java @@ -1,45 +1,45 @@ -package org.thialfihar.android.apg; - -public class CachedPassPhrase { - public final long timestamp; - public final String passPhrase; - - public CachedPassPhrase(long timestamp, String passPhrase) { - super(); - this.timestamp = timestamp; - this.passPhrase = passPhrase; - } - - public int hashCode() { - int hc1 = (int)(this.timestamp & 0xffffffff); - int hc2 = (this.passPhrase == null ? 0 : this.passPhrase.hashCode()); - return (hc1 + hc2) * hc2 + hc1; - } - - public boolean equals(Object other) { - if (!(other instanceof CachedPassPhrase)) { - return false; - } - - CachedPassPhrase o = (CachedPassPhrase) other; - if (timestamp != o.timestamp) { - return false; - } - - if (passPhrase != o.passPhrase) { - if (passPhrase == null || o.passPhrase == null) { - return false; - } - - if (!passPhrase.equals(o.passPhrase)) { - return false; - } - } - - return true; - } - - public String toString() { - return "(" + timestamp + ", *******)"; - } -} +package org.thialfihar.android.apg; + +public class CachedPassPhrase { + public final long timestamp; + public final String passPhrase; + + public CachedPassPhrase(long timestamp, String passPhrase) { + super(); + this.timestamp = timestamp; + this.passPhrase = passPhrase; + } + + public int hashCode() { + int hc1 = (int)(this.timestamp & 0xffffffff); + int hc2 = (this.passPhrase == null ? 0 : this.passPhrase.hashCode()); + return (hc1 + hc2) * hc2 + hc1; + } + + public boolean equals(Object other) { + if (!(other instanceof CachedPassPhrase)) { + return false; + } + + CachedPassPhrase o = (CachedPassPhrase) other; + if (timestamp != o.timestamp) { + return false; + } + + if (passPhrase != o.passPhrase) { + if (passPhrase == null || o.passPhrase == null) { + return false; + } + + if (!passPhrase.equals(o.passPhrase)) { + return false; + } + } + + return true; + } + + public String toString() { + return "(" + timestamp + ", *******)"; + } +} diff --git a/src/org/thialfihar/android/apg/Constants.java b/src/org/thialfihar/android/apg/Constants.java index d4b86029d..4e818b0b9 100644 --- a/src/org/thialfihar/android/apg/Constants.java +++ b/src/org/thialfihar/android/apg/Constants.java @@ -1,35 +1,35 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import android.os.Environment; - -public final class Constants { - public static final class path { - public static final String app_dir = Environment.getExternalStorageDirectory() + "/APG"; - } - - public static final class pref { - public static final String has_seen_change_log = "seenChangeLogDialog"; - public static final String default_encryption_algorithm = "defaultEncryptionAlgorithm"; - public static final String default_hash_algorithm = "defaultHashAlgorithm"; - public static final String default_ascii_armour = "defaultAsciiArmour"; - public static final String default_message_compression = "defaultMessageCompression"; - public static final String default_file_compression = "defaultFileCompression"; - public static final String pass_phrase_cache_ttl = "passPhraseCacheTtl"; - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import android.os.Environment; + +public final class Constants { + public static final class path { + public static final String app_dir = Environment.getExternalStorageDirectory() + "/APG"; + } + + public static final class pref { + public static final String has_seen_change_log = "seenChangeLogDialog"; + public static final String default_encryption_algorithm = "defaultEncryptionAlgorithm"; + public static final String default_hash_algorithm = "defaultHashAlgorithm"; + public static final String default_ascii_armour = "defaultAsciiArmour"; + public static final String default_message_compression = "defaultMessageCompression"; + public static final String default_file_compression = "defaultFileCompression"; + public static final String pass_phrase_cache_ttl = "passPhraseCacheTtl"; + } +} diff --git a/src/org/thialfihar/android/apg/DataDestination.java b/src/org/thialfihar/android/apg/DataDestination.java index 28cacd7ae..a09154c2d 100644 --- a/src/org/thialfihar/android/apg/DataDestination.java +++ b/src/org/thialfihar/android/apg/DataDestination.java @@ -1,79 +1,79 @@ -package org.thialfihar.android.apg; - -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import org.thialfihar.android.apg.Apg.GeneralException; - -import android.content.Context; -import android.os.Environment; - -public class DataDestination { - private String mStreamFilename; - private String mFilename; - private int mMode = Id.mode.undefined; - - public DataDestination() { - - } - - public void setMode(int mode) { - mMode = mode; - } - - public void setFilename(String filename) { - mFilename = filename; - } - - public String getStreamFilename() { - return mStreamFilename; - } - - protected OutputStream getOutputStream(Context context) - throws Apg.GeneralException, FileNotFoundException, IOException { - OutputStream out = null; - mStreamFilename = null; - - switch (mMode) { - case Id.mode.stream: { - try { - while (true) { - mStreamFilename = Apg.generateRandomString(32); - if (mStreamFilename == null) { - throw new Apg.GeneralException("couldn't generate random file name"); - } - context.openFileInput(mStreamFilename).close(); - } - } catch (FileNotFoundException e) { - // found a name that isn't used yet - } - out = context.openFileOutput(mStreamFilename, Context.MODE_PRIVATE); - break; - } - - case Id.mode.byte_array: { - out = new ByteArrayOutputStream(); - break; - } - - case Id.mode.file: { - if (mFilename.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); - } - } - out = new FileOutputStream(mFilename); - break; - } - - default: { - break; - } - } - - return out; - } -} +package org.thialfihar.android.apg; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.thialfihar.android.apg.Apg.GeneralException; + +import android.content.Context; +import android.os.Environment; + +public class DataDestination { + private String mStreamFilename; + private String mFilename; + private int mMode = Id.mode.undefined; + + public DataDestination() { + + } + + public void setMode(int mode) { + mMode = mode; + } + + public void setFilename(String filename) { + mFilename = filename; + } + + public String getStreamFilename() { + return mStreamFilename; + } + + protected OutputStream getOutputStream(Context context) + throws Apg.GeneralException, FileNotFoundException, IOException { + OutputStream out = null; + mStreamFilename = null; + + switch (mMode) { + case Id.mode.stream: { + try { + while (true) { + mStreamFilename = Apg.generateRandomString(32); + if (mStreamFilename == null) { + throw new Apg.GeneralException("couldn't generate random file name"); + } + context.openFileInput(mStreamFilename).close(); + } + } catch (FileNotFoundException e) { + // found a name that isn't used yet + } + out = context.openFileOutput(mStreamFilename, Context.MODE_PRIVATE); + break; + } + + case Id.mode.byte_array: { + out = new ByteArrayOutputStream(); + break; + } + + case Id.mode.file: { + if (mFilename.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); + } + } + out = new FileOutputStream(mFilename); + break; + } + + default: { + break; + } + } + + return out; + } +} diff --git a/src/org/thialfihar/android/apg/DataSource.java b/src/org/thialfihar/android/apg/DataSource.java index 159cdb349..34583ab59 100644 --- a/src/org/thialfihar/android/apg/DataSource.java +++ b/src/org/thialfihar/android/apg/DataSource.java @@ -1,88 +1,88 @@ -package org.thialfihar.android.apg; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -import org.thialfihar.android.apg.Apg.GeneralException; - -import android.content.Context; -import android.net.Uri; -import android.os.Environment; - -public class DataSource { - private Uri mContentUri = null; - private String mText = null; - private byte[] mData = null; - - public DataSource() { - - } - - public void setUri(Uri uri) { - mContentUri = uri; - } - - public void setUri(String uri) { - if (uri.startsWith("/")) { - setUri(Uri.parse("file://" + uri)); - } else { - setUri(Uri.parse(uri)); - } - } - - public void setText(String text) { - mText = text; - } - - public void setData(byte[] data) { - mData = data; - } - - public InputData getInputData(Context context, boolean withSize) - throws GeneralException, FileNotFoundException, IOException { - InputStream in = null; - long size = 0; - - if (mContentUri != null) { - if (mContentUri.getScheme().equals("file")) { - // get the rest after "file://" - String path = mContentUri.toString().substring(6); - if (path.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); - } - } - in = new FileInputStream(path); - File file = new File(path); - if (withSize) { - size = file.length(); - } - } else { - in = context.getContentResolver().openInputStream(mContentUri); - if (withSize) { - InputStream tmp = context.getContentResolver().openInputStream(mContentUri); - size = Apg.getLengthOfStream(tmp); - tmp.close(); - } - } - } else if (mText != null || mData != null) { - byte[] bytes = null; - if (mData != null) { - bytes = mData; - } else { - bytes = mText.getBytes(); - } - in = new ByteArrayInputStream(bytes); - if (withSize) { - size = bytes.length; - } - } - - return new InputData(in, size); - } - -} +package org.thialfihar.android.apg; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import org.thialfihar.android.apg.Apg.GeneralException; + +import android.content.Context; +import android.net.Uri; +import android.os.Environment; + +public class DataSource { + private Uri mContentUri = null; + private String mText = null; + private byte[] mData = null; + + public DataSource() { + + } + + public void setUri(Uri uri) { + mContentUri = uri; + } + + public void setUri(String uri) { + if (uri.startsWith("/")) { + setUri(Uri.parse("file://" + uri)); + } else { + setUri(Uri.parse(uri)); + } + } + + public void setText(String text) { + mText = text; + } + + public void setData(byte[] data) { + mData = data; + } + + public InputData getInputData(Context context, boolean withSize) + throws GeneralException, FileNotFoundException, IOException { + InputStream in = null; + long size = 0; + + if (mContentUri != null) { + if (mContentUri.getScheme().equals("file")) { + // get the rest after "file://" + String path = mContentUri.toString().substring(6); + if (path.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); + } + } + in = new FileInputStream(path); + File file = new File(path); + if (withSize) { + size = file.length(); + } + } else { + in = context.getContentResolver().openInputStream(mContentUri); + if (withSize) { + InputStream tmp = context.getContentResolver().openInputStream(mContentUri); + size = Apg.getLengthOfStream(tmp); + tmp.close(); + } + } + } else if (mText != null || mData != null) { + byte[] bytes = null; + if (mData != null) { + bytes = mData; + } else { + bytes = mText.getBytes(); + } + in = new ByteArrayInputStream(bytes); + if (withSize) { + size = bytes.length; + } + } + + return new InputData(in, size); + } + +} diff --git a/src/org/thialfihar/android/apg/FileDialog.java b/src/org/thialfihar/android/apg/FileDialog.java index b6bbbf3f1..d717bb0bb 100644 --- a/src/org/thialfihar/android/apg/FileDialog.java +++ b/src/org/thialfihar/android/apg/FileDialog.java @@ -1,118 +1,118 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.Toast; - -public class FileDialog { - private static EditText mFilename; - private static ImageButton mBrowse; - private static Activity mActivity; - private static String mFileManagerTitle; - private static String mFileManagerButton; - private static int mRequestCode; - - public static interface OnClickListener { - public void onCancelClick(); - public void onOkClick(String filename); - } - - public static AlertDialog build(Activity activity, String title, String message, - String defaultFile, OnClickListener onClickListener, - String fileManagerTitle, String fileManagerButton, - int requestCode) { - // TODO: fileManagerTitle and fileManagerButton are deprecated, no use for them right now, - // but maybe the Intent now used will someday support them again, so leaving them in - LayoutInflater inflater = - (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - AlertDialog.Builder alert = new AlertDialog.Builder(activity); - - alert.setTitle(title); - alert.setMessage(message); - - View view = (View) inflater.inflate(R.layout.file_dialog, null); - - mActivity = activity; - mFilename = (EditText) view.findViewById(R.id.input); - mFilename.setText(defaultFile); - mBrowse = (ImageButton) view.findViewById(R.id.btn_browse); - mBrowse.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - openFile(); - } - }); - mFileManagerTitle = fileManagerTitle; - mFileManagerButton = fileManagerButton; - mRequestCode = requestCode; - - alert.setView(view); - - final OnClickListener clickListener = onClickListener; - - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - clickListener.onOkClick(mFilename.getText().toString()); - } - }); - - alert.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - clickListener.onCancelClick(); - } - }); - return alert.create(); - } - - public static void setFilename(String filename) { - if (mFilename != null) { - mFilename.setText(filename); - } - } - - /** - * Opens the file manager to select a file to open. - */ - private static void openFile() { - String filename = mFilename.getText().toString(); - - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - - intent.setData(Uri.parse("file://" + filename)); - intent.setType("*/*"); - - try { - mActivity.startActivityForResult(intent, mRequestCode); - } catch (ActivityNotFoundException e) { - // No compatible file manager was found. - Toast.makeText(mActivity, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show(); - } - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.Toast; + +public class FileDialog { + private static EditText mFilename; + private static ImageButton mBrowse; + private static Activity mActivity; + private static String mFileManagerTitle; + private static String mFileManagerButton; + private static int mRequestCode; + + public static interface OnClickListener { + public void onCancelClick(); + public void onOkClick(String filename); + } + + public static AlertDialog build(Activity activity, String title, String message, + String defaultFile, OnClickListener onClickListener, + String fileManagerTitle, String fileManagerButton, + int requestCode) { + // TODO: fileManagerTitle and fileManagerButton are deprecated, no use for them right now, + // but maybe the Intent now used will someday support them again, so leaving them in + LayoutInflater inflater = + (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + AlertDialog.Builder alert = new AlertDialog.Builder(activity); + + alert.setTitle(title); + alert.setMessage(message); + + View view = (View) inflater.inflate(R.layout.file_dialog, null); + + mActivity = activity; + mFilename = (EditText) view.findViewById(R.id.input); + mFilename.setText(defaultFile); + mBrowse = (ImageButton) view.findViewById(R.id.btn_browse); + mBrowse.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + openFile(); + } + }); + mFileManagerTitle = fileManagerTitle; + mFileManagerButton = fileManagerButton; + mRequestCode = requestCode; + + alert.setView(view); + + final OnClickListener clickListener = onClickListener; + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + clickListener.onOkClick(mFilename.getText().toString()); + } + }); + + alert.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + clickListener.onCancelClick(); + } + }); + return alert.create(); + } + + public static void setFilename(String filename) { + if (mFilename != null) { + mFilename.setText(filename); + } + } + + /** + * Opens the file manager to select a file to open. + */ + private static void openFile() { + String filename = mFilename.getText().toString(); + + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + + intent.setData(Uri.parse("file://" + filename)); + intent.setType("*/*"); + + try { + mActivity.startActivityForResult(intent, mRequestCode); + } catch (ActivityNotFoundException e) { + // No compatible file manager was found. + Toast.makeText(mActivity, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show(); + } + } +} diff --git a/src/org/thialfihar/android/apg/GeneralActivity.java b/src/org/thialfihar/android/apg/GeneralActivity.java index 49cbdcf2a..01bd93f92 100644 --- a/src/org/thialfihar/android/apg/GeneralActivity.java +++ b/src/org/thialfihar/android/apg/GeneralActivity.java @@ -1,177 +1,177 @@ -package org.thialfihar.android.apg; - -import java.io.ByteArrayInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Vector; - -import org.thialfihar.android.apg.utils.Choice; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.ListView; -import android.widget.Toast; - -public class GeneralActivity extends BaseActivity { - private Intent mIntent; - private ArrayAdapter mAdapter; - private ListView mList; - private Button mCancelButton; - private String mDataString; - private Uri mDataUri; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.general); - - mIntent = getIntent(); - - InputStream inStream = null; - { - String data = mIntent.getStringExtra(Intent.EXTRA_TEXT); - if (data != null) { - mDataString = data; - inStream = new ByteArrayInputStream(data.getBytes()); - } - } - - if (inStream == null) { - Uri data = mIntent.getData(); - if (data != null) { - mDataUri = data; - try { - inStream = getContentResolver().openInputStream(data); - } catch (FileNotFoundException e) { - // didn't work - Toast.makeText(this, "failed to open stream", Toast.LENGTH_SHORT).show(); - } - } - } - - if (inStream == null) { - Toast.makeText(this, "no data found", Toast.LENGTH_SHORT).show(); - finish(); - return; - } - - int contentType = Id.content.unknown; - try { - contentType = Apg.getStreamContent(this, inStream); - inStream.close(); - } catch (IOException e) { - // just means that there's no PGP data in there - } - - mList = (ListView) findViewById(R.id.options); - Vector choices = new Vector(); - - if (contentType == Id.content.keys) { - choices.add(new Choice(Id.choice.action.import_public, - getString(R.string.action_importPublic))); - choices.add(new Choice(Id.choice.action.import_secret, - getString(R.string.action_importSecret))); - } - - if (contentType == Id.content.encrypted_data) { - choices.add(new Choice(Id.choice.action.decrypt, getString(R.string.action_decrypt))); - } - - if (contentType == Id.content.unknown) { - choices.add(new Choice(Id.choice.action.encrypt, getString(R.string.action_encrypt))); - } - - mAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, choices); - mList.setAdapter(mAdapter); - - mList.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) { - clicked(mAdapter.getItem(arg2).getId()); - } - }); - - mCancelButton = (Button) findViewById(R.id.btn_cancel); - mCancelButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - GeneralActivity.this.finish(); - } - }); - - if (choices.size() == 1) { - clicked(choices.get(0).getId()); - } - } - - private void clicked(int id) { - Intent intent = new Intent(); - switch (id) { - case Id.choice.action.encrypt: { - intent.setClass(this, EncryptActivity.class); - if (mDataString != null) { - intent.setAction(Apg.Intent.ENCRYPT); - intent.putExtra(Apg.EXTRA_TEXT, mDataString); - } else if (mDataUri != null) { - intent.setAction(Apg.Intent.ENCRYPT_FILE); - intent.setDataAndType(mDataUri, mIntent.getType()); - } - - break; - } - - case Id.choice.action.decrypt: { - intent.setClass(this, DecryptActivity.class); - if (mDataString != null) { - intent.setAction(Apg.Intent.DECRYPT); - intent.putExtra(Apg.EXTRA_TEXT, mDataString); - } else if (mDataUri != null) { - intent.setAction(Apg.Intent.DECRYPT_FILE); - intent.setDataAndType(mDataUri, mIntent.getType()); - } - - break; - } - - case Id.choice.action.import_public: { - intent.setClass(this, PublicKeyListActivity.class); - intent.setAction(Apg.Intent.IMPORT); - if (mDataString != null) { - intent.putExtra(Apg.EXTRA_TEXT, mDataString); - } else if (mDataUri != null) { - intent.setDataAndType(mDataUri, mIntent.getType()); - } - break; - } - - case Id.choice.action.import_secret: { - intent.setClass(this, SecretKeyListActivity.class); - intent.setAction(Apg.Intent.IMPORT); - if (mDataString != null) { - intent.putExtra(Apg.EXTRA_TEXT, mDataString); - } else if (mDataUri != null) { - intent.setDataAndType(mDataUri, mIntent.getType()); - } - break; - } - - default: { - // shouldn't happen - return; - } - } - - startActivity(intent); - finish(); - } -} +package org.thialfihar.android.apg; + +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; + +import org.thialfihar.android.apg.utils.Choice; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.Toast; + +public class GeneralActivity extends BaseActivity { + private Intent mIntent; + private ArrayAdapter mAdapter; + private ListView mList; + private Button mCancelButton; + private String mDataString; + private Uri mDataUri; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.general); + + mIntent = getIntent(); + + InputStream inStream = null; + { + String data = mIntent.getStringExtra(Intent.EXTRA_TEXT); + if (data != null) { + mDataString = data; + inStream = new ByteArrayInputStream(data.getBytes()); + } + } + + if (inStream == null) { + Uri data = mIntent.getData(); + if (data != null) { + mDataUri = data; + try { + inStream = getContentResolver().openInputStream(data); + } catch (FileNotFoundException e) { + // didn't work + Toast.makeText(this, "failed to open stream", Toast.LENGTH_SHORT).show(); + } + } + } + + if (inStream == null) { + Toast.makeText(this, "no data found", Toast.LENGTH_SHORT).show(); + finish(); + return; + } + + int contentType = Id.content.unknown; + try { + contentType = Apg.getStreamContent(this, inStream); + inStream.close(); + } catch (IOException e) { + // just means that there's no PGP data in there + } + + mList = (ListView) findViewById(R.id.options); + Vector choices = new Vector(); + + if (contentType == Id.content.keys) { + choices.add(new Choice(Id.choice.action.import_public, + getString(R.string.action_importPublic))); + choices.add(new Choice(Id.choice.action.import_secret, + getString(R.string.action_importSecret))); + } + + if (contentType == Id.content.encrypted_data) { + choices.add(new Choice(Id.choice.action.decrypt, getString(R.string.action_decrypt))); + } + + if (contentType == Id.content.unknown) { + choices.add(new Choice(Id.choice.action.encrypt, getString(R.string.action_encrypt))); + } + + mAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, choices); + mList.setAdapter(mAdapter); + + mList.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) { + clicked(mAdapter.getItem(arg2).getId()); + } + }); + + mCancelButton = (Button) findViewById(R.id.btn_cancel); + mCancelButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + GeneralActivity.this.finish(); + } + }); + + if (choices.size() == 1) { + clicked(choices.get(0).getId()); + } + } + + private void clicked(int id) { + Intent intent = new Intent(); + switch (id) { + case Id.choice.action.encrypt: { + intent.setClass(this, EncryptActivity.class); + if (mDataString != null) { + intent.setAction(Apg.Intent.ENCRYPT); + intent.putExtra(Apg.EXTRA_TEXT, mDataString); + } else if (mDataUri != null) { + intent.setAction(Apg.Intent.ENCRYPT_FILE); + intent.setDataAndType(mDataUri, mIntent.getType()); + } + + break; + } + + case Id.choice.action.decrypt: { + intent.setClass(this, DecryptActivity.class); + if (mDataString != null) { + intent.setAction(Apg.Intent.DECRYPT); + intent.putExtra(Apg.EXTRA_TEXT, mDataString); + } else if (mDataUri != null) { + intent.setAction(Apg.Intent.DECRYPT_FILE); + intent.setDataAndType(mDataUri, mIntent.getType()); + } + + break; + } + + case Id.choice.action.import_public: { + intent.setClass(this, PublicKeyListActivity.class); + intent.setAction(Apg.Intent.IMPORT); + if (mDataString != null) { + intent.putExtra(Apg.EXTRA_TEXT, mDataString); + } else if (mDataUri != null) { + intent.setDataAndType(mDataUri, mIntent.getType()); + } + break; + } + + case Id.choice.action.import_secret: { + intent.setClass(this, SecretKeyListActivity.class); + intent.setAction(Apg.Intent.IMPORT); + if (mDataString != null) { + intent.putExtra(Apg.EXTRA_TEXT, mDataString); + } else if (mDataUri != null) { + intent.setDataAndType(mDataUri, mIntent.getType()); + } + break; + } + + default: { + // shouldn't happen + return; + } + } + + startActivity(intent); + finish(); + } +} diff --git a/src/org/thialfihar/android/apg/Id.java b/src/org/thialfihar/android/apg/Id.java index bc8b7e443..9ff484271 100644 --- a/src/org/thialfihar/android/apg/Id.java +++ b/src/org/thialfihar/android/apg/Id.java @@ -1,155 +1,155 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import org.bouncycastle2.bcpg.CompressionAlgorithmTags; - -public final class Id { - public static final class menu { - public static final int export = 0x21070001; - public static final int delete = 0x21070002; - public static final int edit = 0x21070003; - - public static final class option { - public static final int new_pass_phrase = 0x21070001; - public static final int create = 0x21070002; - public static final int about = 0x21070003; - public static final int manage_public_keys = 0x21070004; - public static final int manage_secret_keys = 0x21070005; - public static final int import_keys = 0x21070006; - public static final int export_keys = 0x21070007; - public static final int preferences = 0x21070008; - public static final int search = 0x21070009; - } - } - - public static final class message { - public static final int progress_update = 0x21070001; - public static final int done = 0x21070002; - public static final int import_keys = 0x21070003; - public static final int export_keys = 0x21070004; - public static final int import_done = 0x21070005; - public static final int export_done = 0x21070006; - public static final int create_key = 0x21070007; - public static final int edit_key = 0x21070008; - } - - public static final class request { - public static final int public_keys = 0x21070001; - public static final int secret_keys = 0x21070002; - public static final int filename = 0x21070003; - public static final int output_filename = 0x21070004; - } - - public static final class dialog { - public static final int pass_phrase = 0x21070001; - public static final int encrypting = 0x21070002; - public static final int decrypting = 0x21070003; - public static final int new_pass_phrase = 0x21070004; - public static final int pass_phrases_do_not_match = 0x21070005; - public static final int no_pass_phrase = 0x21070006; - public static final int saving = 0x21070007; - public static final int delete_key = 0x21070008; - public static final int import_keys = 0x21070009; - public static final int importing = 0x2107000a; - public static final int export_key = 0x2107000b; - public static final int export_keys = 0x2107000c; - public static final int exporting = 0x2107000d; - public static final int new_account = 0x2107000e; - public static final int about = 0x2107000f; - public static final int change_log = 0x21070010; - public static final int output_filename = 0x21070011; - public static final int delete_file = 0x21070012; - } - - public static final class task { - public static final int import_keys = 0x21070001; - public static final int export_keys = 0x21070002; - } - - public static final class database { - public static final int type_public = 0; - public static final int type_secret = 1; - } - - public static final class type { - public static final int public_key = 0x21070001; - public static final int secret_key = 0x21070002; - public static final int user_id = 0x21070003; - public static final int key = 0x21070004; - } - - public static final class choice { - public static final class algorithm { - public static final int dsa = 0x21070001; - public static final int elgamal = 0x21070002; - public static final int rsa = 0x21070003; - } - - public static final class compression { - public static final int none = 0x21070001; - public static final int zlib = CompressionAlgorithmTags.ZLIB; - public static final int bzip2 = CompressionAlgorithmTags.BZIP2; - public static final int zip = CompressionAlgorithmTags.ZIP; - } - - public static final class usage { - public static final int sign_only = 0x21070001; - public static final int encrypt_only = 0x21070002; - public static final int sign_and_encrypt = 0x21070003; - } - - public static final class action { - public static final int encrypt = 0x21070001; - public static final int decrypt = 0x21070002; - public static final int import_public = 0x21070003; - public static final int import_secret = 0x21070004; - } - } - - public static final class return_value { - public static final int ok = 0; - public static final int error = -1; - public static final int no_master_key = -2; - public static final int updated = 1; - } - - public static final class target { - public static final int clipboard = 0x21070001; - public static final int email = 0x21070002; - public static final int file = 0x21070003; - public static final int message = 0x21070004; - } - - public static final class mode { - public static final int undefined = 0x21070001; - public static final int byte_array = 0x21070002; - public static final int file = 0x21070003; - public static final int stream = 0x21070004; - } - - public static final class key { - public static final int none = 0; - public static final int symmetric = -1; - } - - public static final class content { - public static final int unknown = 0; - public static final int encrypted_data = 1; - public static final int keys = 2; - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import org.bouncycastle2.bcpg.CompressionAlgorithmTags; + +public final class Id { + public static final class menu { + public static final int export = 0x21070001; + public static final int delete = 0x21070002; + public static final int edit = 0x21070003; + + public static final class option { + public static final int new_pass_phrase = 0x21070001; + public static final int create = 0x21070002; + public static final int about = 0x21070003; + public static final int manage_public_keys = 0x21070004; + public static final int manage_secret_keys = 0x21070005; + public static final int import_keys = 0x21070006; + public static final int export_keys = 0x21070007; + public static final int preferences = 0x21070008; + public static final int search = 0x21070009; + } + } + + public static final class message { + public static final int progress_update = 0x21070001; + public static final int done = 0x21070002; + public static final int import_keys = 0x21070003; + public static final int export_keys = 0x21070004; + public static final int import_done = 0x21070005; + public static final int export_done = 0x21070006; + public static final int create_key = 0x21070007; + public static final int edit_key = 0x21070008; + } + + public static final class request { + public static final int public_keys = 0x21070001; + public static final int secret_keys = 0x21070002; + public static final int filename = 0x21070003; + public static final int output_filename = 0x21070004; + } + + public static final class dialog { + public static final int pass_phrase = 0x21070001; + public static final int encrypting = 0x21070002; + public static final int decrypting = 0x21070003; + public static final int new_pass_phrase = 0x21070004; + public static final int pass_phrases_do_not_match = 0x21070005; + public static final int no_pass_phrase = 0x21070006; + public static final int saving = 0x21070007; + public static final int delete_key = 0x21070008; + public static final int import_keys = 0x21070009; + public static final int importing = 0x2107000a; + public static final int export_key = 0x2107000b; + public static final int export_keys = 0x2107000c; + public static final int exporting = 0x2107000d; + public static final int new_account = 0x2107000e; + public static final int about = 0x2107000f; + public static final int change_log = 0x21070010; + public static final int output_filename = 0x21070011; + public static final int delete_file = 0x21070012; + } + + public static final class task { + public static final int import_keys = 0x21070001; + public static final int export_keys = 0x21070002; + } + + public static final class database { + public static final int type_public = 0; + public static final int type_secret = 1; + } + + public static final class type { + public static final int public_key = 0x21070001; + public static final int secret_key = 0x21070002; + public static final int user_id = 0x21070003; + public static final int key = 0x21070004; + } + + public static final class choice { + public static final class algorithm { + public static final int dsa = 0x21070001; + public static final int elgamal = 0x21070002; + public static final int rsa = 0x21070003; + } + + public static final class compression { + public static final int none = 0x21070001; + public static final int zlib = CompressionAlgorithmTags.ZLIB; + public static final int bzip2 = CompressionAlgorithmTags.BZIP2; + public static final int zip = CompressionAlgorithmTags.ZIP; + } + + public static final class usage { + public static final int sign_only = 0x21070001; + public static final int encrypt_only = 0x21070002; + public static final int sign_and_encrypt = 0x21070003; + } + + public static final class action { + public static final int encrypt = 0x21070001; + public static final int decrypt = 0x21070002; + public static final int import_public = 0x21070003; + public static final int import_secret = 0x21070004; + } + } + + public static final class return_value { + public static final int ok = 0; + public static final int error = -1; + public static final int no_master_key = -2; + public static final int updated = 1; + } + + public static final class target { + public static final int clipboard = 0x21070001; + public static final int email = 0x21070002; + public static final int file = 0x21070003; + public static final int message = 0x21070004; + } + + public static final class mode { + public static final int undefined = 0x21070001; + public static final int byte_array = 0x21070002; + public static final int file = 0x21070003; + public static final int stream = 0x21070004; + } + + public static final class key { + public static final int none = 0; + public static final int symmetric = -1; + } + + public static final class content { + public static final int unknown = 0; + public static final int encrypted_data = 1; + public static final int keys = 2; + } +} diff --git a/src/org/thialfihar/android/apg/InputData.java b/src/org/thialfihar/android/apg/InputData.java index cf49f1c33..49f66d5b6 100644 --- a/src/org/thialfihar/android/apg/InputData.java +++ b/src/org/thialfihar/android/apg/InputData.java @@ -1,25 +1,25 @@ -package org.thialfihar.android.apg; - -import java.io.InputStream; - -public class InputData { - private PositionAwareInputStream mInputStream; - private long mSize; - - InputData(InputStream inputStream, long size) { - mInputStream = new PositionAwareInputStream(inputStream); - mSize = size; - } - - public InputStream getInputStream() { - return mInputStream; - } - - public long getSize() { - return mSize; - } - - public long getStreamPosition() { - return mInputStream.position(); - } -} +package org.thialfihar.android.apg; + +import java.io.InputStream; + +public class InputData { + private PositionAwareInputStream mInputStream; + private long mSize; + + InputData(InputStream inputStream, long size) { + mInputStream = new PositionAwareInputStream(inputStream); + mSize = size; + } + + public InputStream getInputStream() { + return mInputStream; + } + + public long getSize() { + return mSize; + } + + public long getStreamPosition() { + return mInputStream.position(); + } +} diff --git a/src/org/thialfihar/android/apg/IntegerListPreference.java b/src/org/thialfihar/android/apg/IntegerListPreference.java index 26a58afd5..0c5a643ef 100644 --- a/src/org/thialfihar/android/apg/IntegerListPreference.java +++ b/src/org/thialfihar/android/apg/IntegerListPreference.java @@ -1,95 +1,95 @@ -/* - * Copyright 2010 Google Inc. - * - * 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.thialfihar.android.apg; - -import android.content.Context; -import android.preference.ListPreference; -import android.util.AttributeSet; - -/** - * A list preference which persists its values as integers instead of strings. - * Code reading the values should use - * {@link android.content.SharedPreferences#getInt}. - * When using XML-declared arrays for entry values, the arrays should be regular - * string arrays containing valid integer values. - * - * @author Rodrigo Damazio - */ -public class IntegerListPreference extends ListPreference { - - public IntegerListPreference(Context context) { - super(context); - - verifyEntryValues(null); - } - - public IntegerListPreference(Context context, AttributeSet attrs) { - super(context, attrs); - - verifyEntryValues(null); - } - - @Override - public void setEntryValues(CharSequence[] entryValues) { - CharSequence[] oldValues = getEntryValues(); - super.setEntryValues(entryValues); - verifyEntryValues(oldValues); - } - - @Override - public void setEntryValues(int entryValuesResId) { - CharSequence[] oldValues = getEntryValues(); - super.setEntryValues(entryValuesResId); - verifyEntryValues(oldValues); - } - - @Override - protected String getPersistedString(String defaultReturnValue) { - // During initial load, there's no known default value - int defaultIntegerValue = Integer.MIN_VALUE; - if (defaultReturnValue != null) { - defaultIntegerValue = Integer.parseInt(defaultReturnValue); - } - - // When the list preference asks us to read a string, instead read an - // integer. - int value = getPersistedInt(defaultIntegerValue); - return Integer.toString(value); - } - - @Override - protected boolean persistString(String value) { - // When asked to save a string, instead save an integer - return persistInt(Integer.parseInt(value)); - } - - private void verifyEntryValues(CharSequence[] oldValues) { - CharSequence[] entryValues = getEntryValues(); - if (entryValues == null) { - return; - } - - for (CharSequence entryValue : entryValues) { - try { - Integer.parseInt(entryValue.toString()); - } catch (NumberFormatException nfe) { - super.setEntryValues(oldValues); - throw nfe; - } - } - } -} +/* + * Copyright 2010 Google Inc. + * + * 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.thialfihar.android.apg; + +import android.content.Context; +import android.preference.ListPreference; +import android.util.AttributeSet; + +/** + * A list preference which persists its values as integers instead of strings. + * Code reading the values should use + * {@link android.content.SharedPreferences#getInt}. + * When using XML-declared arrays for entry values, the arrays should be regular + * string arrays containing valid integer values. + * + * @author Rodrigo Damazio + */ +public class IntegerListPreference extends ListPreference { + + public IntegerListPreference(Context context) { + super(context); + + verifyEntryValues(null); + } + + public IntegerListPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + verifyEntryValues(null); + } + + @Override + public void setEntryValues(CharSequence[] entryValues) { + CharSequence[] oldValues = getEntryValues(); + super.setEntryValues(entryValues); + verifyEntryValues(oldValues); + } + + @Override + public void setEntryValues(int entryValuesResId) { + CharSequence[] oldValues = getEntryValues(); + super.setEntryValues(entryValuesResId); + verifyEntryValues(oldValues); + } + + @Override + protected String getPersistedString(String defaultReturnValue) { + // During initial load, there's no known default value + int defaultIntegerValue = Integer.MIN_VALUE; + if (defaultReturnValue != null) { + defaultIntegerValue = Integer.parseInt(defaultReturnValue); + } + + // When the list preference asks us to read a string, instead read an + // integer. + int value = getPersistedInt(defaultIntegerValue); + return Integer.toString(value); + } + + @Override + protected boolean persistString(String value) { + // When asked to save a string, instead save an integer + return persistInt(Integer.parseInt(value)); + } + + private void verifyEntryValues(CharSequence[] oldValues) { + CharSequence[] entryValues = getEntryValues(); + if (entryValues == null) { + return; + } + + for (CharSequence entryValue : entryValues) { + try { + Integer.parseInt(entryValue.toString()); + } catch (NumberFormatException nfe) { + super.setEntryValues(oldValues); + throw nfe; + } + } + } +} diff --git a/src/org/thialfihar/android/apg/KeyListActivity.java b/src/org/thialfihar/android/apg/KeyListActivity.java index 26c744f33..fc074e75d 100644 --- a/src/org/thialfihar/android/apg/KeyListActivity.java +++ b/src/org/thialfihar/android/apg/KeyListActivity.java @@ -1,762 +1,762 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Vector; - -import org.bouncycastle2.openpgp.PGPException; -import org.bouncycastle2.openpgp.PGPPublicKeyRing; -import org.bouncycastle2.openpgp.PGPSecretKeyRing; -import org.thialfihar.android.apg.provider.KeyRings; -import org.thialfihar.android.apg.provider.Keys; -import org.thialfihar.android.apg.provider.UserIds; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.SearchManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.os.Bundle; -import android.os.Message; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.BaseExpandableListAdapter; -import android.widget.Button; -import android.widget.ExpandableListView; -import android.widget.ExpandableListView.ExpandableListContextMenuInfo; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -public class KeyListActivity extends BaseActivity { - protected ExpandableListView mList; - protected KeyListAdapter mListAdapter; - protected View mFilterLayout; - protected Button mClearFilterButton; - protected TextView mFilterInfo; - - protected int mSelectedItem = -1; - protected int mTask = 0; - - protected String mImportFilename = Constants.path.app_dir + "/"; - protected String mExportFilename = Constants.path.app_dir + "/"; - - protected String mImportData; - - protected int mKeyType = Id.type.public_key; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.key_list); - - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - mList = (ExpandableListView) findViewById(R.id.list); - registerForContextMenu(mList); - - mFilterLayout = (View) findViewById(R.id.layout_filter); - mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); - mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); - - mClearFilterButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - handleIntent(new Intent()); - } - }); - - handleIntent(getIntent()); - - Intent intent = getIntent(); - if (Apg.Intent.IMPORT.equals(intent.getAction())) { - if ("file".equals(intent.getScheme()) && intent.getDataString() != null) { - mImportFilename = intent.getDataString().replace("file://", ""); - } else { - mImportData = intent.getStringExtra(Apg.EXTRA_TEXT); - } - importKeys(); - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - 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)); - } - - if (mListAdapter != null) { - mListAdapter.cleanup(); - } - mListAdapter = new KeyListAdapter(this, searchString); - mList.setAdapter(mListAdapter); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case Id.menu.option.import_keys: { - showDialog(Id.dialog.import_keys); - return true; - } - - case Id.menu.option.export_keys: { - showDialog(Id.dialog.export_keys); - return true; - } - - default: { - return super.onOptionsItemSelected(item); - } - } - } - - @Override - public boolean onContextItemSelected(MenuItem menuItem) { - ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); - int type = ExpandableListView.getPackedPositionType(info.packedPosition); - int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); - - if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) { - return super.onContextItemSelected(menuItem); - } - - switch (menuItem.getItemId()) { - case Id.menu.export: { - mSelectedItem = groupPosition; - showDialog(Id.dialog.export_key); - return true; - } - - case Id.menu.delete: { - mSelectedItem = groupPosition; - showDialog(Id.dialog.delete_key); - return true; - } - - default: { - return super.onContextItemSelected(menuItem); - } - } - } - - @Override - protected Dialog onCreateDialog(int id) { - boolean singleKeyExport = false; - - switch (id) { - case Id.dialog.delete_key: { - final int keyRingId = mListAdapter.getKeyRingId(mSelectedItem); - mSelectedItem = -1; - // TODO: better way to do this? - String userId = ""; - Object keyRing = Apg.getKeyRing(keyRingId); - if (keyRing != null) { - if (keyRing instanceof PGPPublicKeyRing) { - userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey((PGPPublicKeyRing) keyRing)); - } else { - userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey((PGPSecretKeyRing) keyRing)); - } - } - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.warning); - builder.setMessage(getString(mKeyType == Id.type.public_key ? - R.string.keyDeletionConfirmation : - R.string.secretKeyDeletionConfirmation, userId)); - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setPositiveButton(R.string.btn_delete, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - deleteKey(keyRingId); - removeDialog(Id.dialog.delete_key); - } - }); - builder.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.delete_key); - } - }); - return builder.create(); - } - - case Id.dialog.import_keys: { - return FileDialog.build(this, getString(R.string.title_importKeys), - getString(R.string.specifyFileToImportFrom), - mImportFilename, - new FileDialog.OnClickListener() { - - @Override - public void onOkClick(String filename) { - removeDialog(Id.dialog.import_keys); - mImportFilename = filename; - importKeys(); - } - - @Override - public void onCancelClick() { - removeDialog(Id.dialog.import_keys); - } - }, - getString(R.string.filemanager_titleOpen), - getString(R.string.filemanager_btnOpen), - Id.request.filename); - } - - case Id.dialog.export_key: { - singleKeyExport = true; - // break intentionally omitted, to use the Id.dialog.export_keys dialog - } - - case Id.dialog.export_keys: { - String title = (singleKeyExport ? - getString(R.string.title_exportKey) : - getString(R.string.title_exportKeys)); - - final int thisDialogId = (singleKeyExport ? Id.dialog.export_key : Id.dialog.export_keys); - - return FileDialog.build(this, title, - getString(mKeyType == Id.type.public_key ? - R.string.specifyFileToExportTo : - R.string.specifyFileToExportSecretKeysTo), - mExportFilename, - new FileDialog.OnClickListener() { - @Override - public void onOkClick(String filename) { - removeDialog(thisDialogId); - mExportFilename = filename; - exportKeys(); - } - - @Override - public void onCancelClick() { - removeDialog(thisDialogId); - } - }, - getString(R.string.filemanager_titleSave), - getString(R.string.filemanager_btnSave), - Id.request.filename); - } - - default: { - return super.onCreateDialog(id); - } - } - } - - public void importKeys() { - showDialog(Id.dialog.importing); - mTask = Id.task.import_keys; - startThread(); - } - - public void exportKeys() { - showDialog(Id.dialog.exporting); - mTask = Id.task.export_keys; - startThread(); - } - - @Override - public void run() { - String error = null; - Bundle data = new Bundle(); - Message msg = new Message(); - - try { - InputStream importInputStream = null; - OutputStream exportOutputStream = null; - long size = 0; - if (mTask == Id.task.import_keys) { - if (mImportData != null) { - byte[] bytes = mImportData.getBytes(); - size = bytes.length; - importInputStream = new ByteArrayInputStream(bytes); - } else { - File file = new File(mImportFilename); - size = file.length(); - importInputStream = new FileInputStream(file); - } - } else { - exportOutputStream = new FileOutputStream(mExportFilename); - } - - if (mTask == Id.task.import_keys) { - data = Apg.importKeyRings(this, mKeyType, new InputData(importInputStream, size), this); - } else { - Vector keyRingIds = new Vector(); - if (mSelectedItem == -1) { - keyRingIds = Apg.getKeyRingIds(mKeyType == Id.type.public_key ? - Id.database.type_public : - Id.database.type_secret); - } else { - int keyRingId = mListAdapter.getKeyRingId(mSelectedItem); - keyRingIds.add(keyRingId); - mSelectedItem = -1; - } - data = Apg.exportKeyRings(this, keyRingIds, exportOutputStream, this); - } - } catch (FileNotFoundException e) { - error = getString(R.string.error_fileNotFound); - } catch (IOException e) { - error = "" + e; - } catch (PGPException e) { - error = "" + e; - } catch (Apg.GeneralException e) { - error = "" + e; - } - - mImportData = null; - - if (mTask == Id.task.import_keys) { - data.putInt(Apg.EXTRA_STATUS, Id.message.import_done); - } else { - data.putInt(Apg.EXTRA_STATUS, Id.message.export_done); - } - - if (error != null) { - data.putString(Apg.EXTRA_ERROR, error); - } - - msg.setData(data); - sendMessage(msg); - } - - protected void deleteKey(int keyRingId) { - Apg.deleteKey(keyRingId); - refreshList(); - } - - protected void refreshList() { - mListAdapter.rebuild(true); - mListAdapter.notifyDataSetChanged(); - } - - @Override - public void doneCallback(Message msg) { - super.doneCallback(msg); - - Bundle data = msg.getData(); - if (data != null) { - int type = data.getInt(Apg.EXTRA_STATUS); - switch (type) { - case Id.message.import_done: { - removeDialog(Id.dialog.importing); - - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(KeyListActivity.this, - getString(R.string.errorMessage, error), - Toast.LENGTH_SHORT).show(); - } else { - int added = data.getInt("added"); - int updated = data.getInt("updated"); - String message; - if (added > 0 && updated > 0) { - message = getString(R.string.keysAddedAndUpdated, added, updated); - } else if (added > 0) { - message = getString(R.string.keysAdded, added); - } else if (updated > 0) { - message = getString(R.string.keysUpdated, updated); - } else { - message = getString(R.string.noKeysAddedOrUpdated); - } - Toast.makeText(KeyListActivity.this, message, - Toast.LENGTH_SHORT).show(); - } - refreshList(); - break; - } - - case Id.message.export_done: { - removeDialog(Id.dialog.exporting); - - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(KeyListActivity.this, - getString(R.string.errorMessage, error), - Toast.LENGTH_SHORT).show(); - } else { - int exported = data.getInt("exported"); - String message; - if (exported == 1) { - message = getString(R.string.keyExported); - } else if (exported > 0) { - message = getString(R.string.keysExported, exported); - } else{ - message = getString(R.string.noKeysExported); - } - Toast.makeText(KeyListActivity.this, message, - Toast.LENGTH_SHORT).show(); - } - break; - } - - default: { - break; - } - } - } - } - - protected class KeyListAdapter extends BaseExpandableListAdapter { - private LayoutInflater mInflater; - private Vector> mChildren; - private SQLiteDatabase mDatabase; - private Cursor mCursor; - private String mSearchString; - - private class KeyChild { - public static final int KEY = 0; - public static final int USER_ID = 1; - - public int type; - public String userId; - public long keyId; - public boolean isMasterKey; - public int algorithm; - public int keySize; - public boolean canSign; - public boolean canEncrypt; - - public KeyChild(long keyId, boolean isMasterKey, int algorithm, int keySize, - boolean canSign, boolean canEncrypt) { - this.keyId = keyId; - this.isMasterKey = isMasterKey; - this.algorithm = algorithm; - this.keySize = keySize; - this.canSign = canSign; - this.canEncrypt = canEncrypt; - } - - public KeyChild(String userId) { - type = USER_ID; - this.userId = userId; - } - } - - public KeyListAdapter(Context context, String searchString) { - mSearchString = searchString; - - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mDatabase = Apg.getDatabase().db(); - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + - "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + - Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + - ") " + - " INNER JOIN " + UserIds.TABLE_NAME + " ON " + - "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + - UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + - UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0')"); - - if (searchString != null && searchString.trim().length() > 0) { - String[] chunks = searchString.trim().split(" +"); - qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + - UserIds.TABLE_NAME + " AS tmp WHERE " + - "tmp." + UserIds.KEY_ID + " = " + - Keys.TABLE_NAME + "." + Keys._ID); - for (int i = 0; i < chunks.length; ++i) { - qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); - qb.appendWhereEscapeString("%" + chunks[i] + "%"); - } - qb.appendWhere(")"); - } - - mCursor = qb.query(mDatabase, - new String[] { - KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 - KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 - UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 - }, - KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", - new String[] { "" + (mKeyType == Id.type.public_key ? - Id.database.type_public : Id.database.type_secret) }, - null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"); - - // content provider way for reference, might have to go back to it sometime: - /*Uri contentUri = null; - if (mKeyType == Id.type.secret_key) { - contentUri = Apg.CONTENT_URI_SECRET_KEY_RINGS; - } else { - contentUri = Apg.CONTENT_URI_PUBLIC_KEY_RINGS; - } - mCursor = getContentResolver().query( - contentUri, - new String[] { - DataProvider._ID, // 0 - DataProvider.MASTER_KEY_ID, // 1 - DataProvider.USER_ID, // 2 - }, - null, null, null);*/ - - startManagingCursor(mCursor); - rebuild(false); - } - - public void cleanup() { - if (mCursor != null) { - stopManagingCursor(mCursor); - mCursor.close(); - } - } - - public void rebuild(boolean requery) { - if (requery) { - mCursor.requery(); - } - mChildren = new Vector>(); - for (int i = 0; i < mCursor.getCount(); ++i) { - mChildren.add(null); - } - } - - protected Vector getChildrenOfGroup(int groupPosition) { - Vector children = mChildren.get(groupPosition); - if (children != null) { - return children; - } - - mCursor.moveToPosition(groupPosition); - children = new Vector(); - Cursor c = mDatabase.query(Keys.TABLE_NAME, - new String[] { - Keys._ID, // 0 - Keys.KEY_ID, // 1 - Keys.IS_MASTER_KEY, // 2 - Keys.ALGORITHM, // 3 - Keys.KEY_SIZE, // 4 - Keys.CAN_SIGN, // 5 - Keys.CAN_ENCRYPT, // 6 - }, - Keys.KEY_RING_ID + " = ?", - new String[] { mCursor.getString(0) }, - null, null, Keys.RANK + " ASC"); - - long masterKeyId = -1; - for (int i = 0; i < c.getCount(); ++i) { - c.moveToPosition(i); - children.add(new KeyChild(c.getLong(1), c.getInt(2) == 1, c.getInt(3), c.getInt(4), - c.getInt(5) == 1, c.getInt(6) == 1)); - if (i == 0) { - masterKeyId = c.getInt(0); - } - } - c.close(); - - if (masterKeyId != -1) { - c = mDatabase.query(UserIds.TABLE_NAME, - new String[] { - UserIds.USER_ID, // 0 - }, - UserIds.KEY_ID + " = ? AND " + UserIds.RANK + " > 0", - new String[] { "" + masterKeyId }, - null, null, UserIds.RANK + " ASC"); - - for (int i = 0; i < c.getCount(); ++i) { - c.moveToPosition(i); - children.add(new KeyChild(c.getString(0))); - } - c.close(); - } - - mChildren.set(groupPosition, children); - return children; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public boolean isChildSelectable(int groupPosition, int childPosition) { - return true; - } - - public int getGroupCount() { - return mCursor.getCount(); - } - - public Object getChild(int groupPosition, int childPosition) { - return null; - } - - public long getChildId(int groupPosition, int childPosition) { - return childPosition; - } - - public int getChildrenCount(int groupPosition) { - return getChildrenOfGroup(groupPosition).size(); - } - - public Object getGroup(int position) { - return position; - } - - public long getGroupId(int position) { - mCursor.moveToPosition(position); - return mCursor.getLong(1); // MASTER_KEY_ID - } - - public int getKeyRingId(int position) { - mCursor.moveToPosition(position); - return mCursor.getInt(0); // _ID - } - - public View getGroupView(int groupPosition, boolean isExpanded, View convertView, - ViewGroup parent) { - mCursor.moveToPosition(groupPosition); - - View view = mInflater.inflate(R.layout.key_list_group_item, null); - view.setBackgroundResource(android.R.drawable.list_selector_background); - - TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(""); - TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); - - String userId = mCursor.getString(2); // USER_ID - if (userId != null) { - String chunks[] = userId.split(" <", 2); - userId = chunks[0]; - if (chunks.length > 1) { - mainUserIdRest.setText("<" + chunks[1]); - } - mainUserId.setText(userId); - } - - if (mainUserId.getText().length() == 0) { - mainUserId.setText(R.string.unknownUserId); - } - - if (mainUserIdRest.getText().length() == 0) { - mainUserIdRest.setVisibility(View.GONE); - } - return view; - } - - public View getChildView(int groupPosition, int childPosition, - boolean isLastChild, View convertView, - ViewGroup parent) { - mCursor.moveToPosition(groupPosition); - - Vector children = getChildrenOfGroup(groupPosition); - - KeyChild child = children.get(childPosition); - View view = null; - switch (child.type) { - case KeyChild.KEY: { - if (child.isMasterKey) { - view = mInflater.inflate(R.layout.key_list_child_item_master_key, null); - } else { - view = mInflater.inflate(R.layout.key_list_child_item_sub_key, null); - } - - TextView keyId = (TextView) view.findViewById(R.id.keyId); - String keyIdStr = Long.toHexString(child.keyId & 0xffffffffL); - while (keyIdStr.length() < 8) { - keyIdStr = "0" + keyIdStr; - } - keyId.setText(keyIdStr); - TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails); - String algorithmStr = Apg.getAlgorithmInfo(child.algorithm, child.keySize); - keyDetails.setText("(" + algorithmStr + ")"); - - ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); - if (!child.canEncrypt) { - encryptIcon.setVisibility(View.GONE); - } - - ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); - if (!child.canSign) { - signIcon.setVisibility(View.GONE); - } - break; - } - - case KeyChild.USER_ID: { - view = mInflater.inflate(R.layout.key_list_child_item_user_id, null); - TextView userId = (TextView) view.findViewById(R.id.userId); - userId.setText(child.userId); - break; - } - } - return view; - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.filename: { - if (resultCode == RESULT_OK && data != null) { - String filename = data.getDataString(); - if (filename != null) { - // Get rid of URI prefix: - if (filename.startsWith("file://")) { - filename = filename.substring(7); - } - // replace %20 and so on - filename = Uri.decode(filename); - - FileDialog.setFilename(filename); - } - } - return; - } - - default: { - break; - } - } - super.onActivityResult(requestCode, resultCode, data); - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +import org.bouncycastle2.openpgp.PGPException; +import org.bouncycastle2.openpgp.PGPPublicKeyRing; +import org.bouncycastle2.openpgp.PGPSecretKeyRing; +import org.thialfihar.android.apg.provider.KeyRings; +import org.thialfihar.android.apg.provider.Keys; +import org.thialfihar.android.apg.provider.UserIds; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.SearchManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.Button; +import android.widget.ExpandableListView; +import android.widget.ExpandableListView.ExpandableListContextMenuInfo; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +public class KeyListActivity extends BaseActivity { + protected ExpandableListView mList; + protected KeyListAdapter mListAdapter; + protected View mFilterLayout; + protected Button mClearFilterButton; + protected TextView mFilterInfo; + + protected int mSelectedItem = -1; + protected int mTask = 0; + + protected String mImportFilename = Constants.path.app_dir + "/"; + protected String mExportFilename = Constants.path.app_dir + "/"; + + protected String mImportData; + + protected int mKeyType = Id.type.public_key; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.key_list); + + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + + mList = (ExpandableListView) findViewById(R.id.list); + registerForContextMenu(mList); + + mFilterLayout = (View) findViewById(R.id.layout_filter); + mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); + mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); + + mClearFilterButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + handleIntent(new Intent()); + } + }); + + handleIntent(getIntent()); + + Intent intent = getIntent(); + if (Apg.Intent.IMPORT.equals(intent.getAction())) { + if ("file".equals(intent.getScheme()) && intent.getDataString() != null) { + mImportFilename = intent.getDataString().replace("file://", ""); + } else { + mImportData = intent.getStringExtra(Apg.EXTRA_TEXT); + } + importKeys(); + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + } + + private void handleIntent(Intent intent) { + 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)); + } + + if (mListAdapter != null) { + mListAdapter.cleanup(); + } + mListAdapter = new KeyListAdapter(this, searchString); + mList.setAdapter(mListAdapter); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Id.menu.option.import_keys: { + showDialog(Id.dialog.import_keys); + return true; + } + + case Id.menu.option.export_keys: { + showDialog(Id.dialog.export_keys); + return true; + } + + default: { + return super.onOptionsItemSelected(item); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); + int type = ExpandableListView.getPackedPositionType(info.packedPosition); + int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); + + if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + return super.onContextItemSelected(menuItem); + } + + switch (menuItem.getItemId()) { + case Id.menu.export: { + mSelectedItem = groupPosition; + showDialog(Id.dialog.export_key); + return true; + } + + case Id.menu.delete: { + mSelectedItem = groupPosition; + showDialog(Id.dialog.delete_key); + return true; + } + + default: { + return super.onContextItemSelected(menuItem); + } + } + } + + @Override + protected Dialog onCreateDialog(int id) { + boolean singleKeyExport = false; + + switch (id) { + case Id.dialog.delete_key: { + final int keyRingId = mListAdapter.getKeyRingId(mSelectedItem); + mSelectedItem = -1; + // TODO: better way to do this? + String userId = ""; + Object keyRing = Apg.getKeyRing(keyRingId); + if (keyRing != null) { + if (keyRing instanceof PGPPublicKeyRing) { + userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey((PGPPublicKeyRing) keyRing)); + } else { + userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey((PGPSecretKeyRing) keyRing)); + } + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.warning); + builder.setMessage(getString(mKeyType == Id.type.public_key ? + R.string.keyDeletionConfirmation : + R.string.secretKeyDeletionConfirmation, userId)); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setPositiveButton(R.string.btn_delete, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + deleteKey(keyRingId); + removeDialog(Id.dialog.delete_key); + } + }); + builder.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.delete_key); + } + }); + return builder.create(); + } + + case Id.dialog.import_keys: { + return FileDialog.build(this, getString(R.string.title_importKeys), + getString(R.string.specifyFileToImportFrom), + mImportFilename, + new FileDialog.OnClickListener() { + + @Override + public void onOkClick(String filename) { + removeDialog(Id.dialog.import_keys); + mImportFilename = filename; + importKeys(); + } + + @Override + public void onCancelClick() { + removeDialog(Id.dialog.import_keys); + } + }, + getString(R.string.filemanager_titleOpen), + getString(R.string.filemanager_btnOpen), + Id.request.filename); + } + + case Id.dialog.export_key: { + singleKeyExport = true; + // break intentionally omitted, to use the Id.dialog.export_keys dialog + } + + case Id.dialog.export_keys: { + String title = (singleKeyExport ? + getString(R.string.title_exportKey) : + getString(R.string.title_exportKeys)); + + final int thisDialogId = (singleKeyExport ? Id.dialog.export_key : Id.dialog.export_keys); + + return FileDialog.build(this, title, + getString(mKeyType == Id.type.public_key ? + R.string.specifyFileToExportTo : + R.string.specifyFileToExportSecretKeysTo), + mExportFilename, + new FileDialog.OnClickListener() { + @Override + public void onOkClick(String filename) { + removeDialog(thisDialogId); + mExportFilename = filename; + exportKeys(); + } + + @Override + public void onCancelClick() { + removeDialog(thisDialogId); + } + }, + getString(R.string.filemanager_titleSave), + getString(R.string.filemanager_btnSave), + Id.request.filename); + } + + default: { + return super.onCreateDialog(id); + } + } + } + + public void importKeys() { + showDialog(Id.dialog.importing); + mTask = Id.task.import_keys; + startThread(); + } + + public void exportKeys() { + showDialog(Id.dialog.exporting); + mTask = Id.task.export_keys; + startThread(); + } + + @Override + public void run() { + String error = null; + Bundle data = new Bundle(); + Message msg = new Message(); + + try { + InputStream importInputStream = null; + OutputStream exportOutputStream = null; + long size = 0; + if (mTask == Id.task.import_keys) { + if (mImportData != null) { + byte[] bytes = mImportData.getBytes(); + size = bytes.length; + importInputStream = new ByteArrayInputStream(bytes); + } else { + File file = new File(mImportFilename); + size = file.length(); + importInputStream = new FileInputStream(file); + } + } else { + exportOutputStream = new FileOutputStream(mExportFilename); + } + + if (mTask == Id.task.import_keys) { + data = Apg.importKeyRings(this, mKeyType, new InputData(importInputStream, size), this); + } else { + Vector keyRingIds = new Vector(); + if (mSelectedItem == -1) { + keyRingIds = Apg.getKeyRingIds(mKeyType == Id.type.public_key ? + Id.database.type_public : + Id.database.type_secret); + } else { + int keyRingId = mListAdapter.getKeyRingId(mSelectedItem); + keyRingIds.add(keyRingId); + mSelectedItem = -1; + } + data = Apg.exportKeyRings(this, keyRingIds, exportOutputStream, this); + } + } catch (FileNotFoundException e) { + error = getString(R.string.error_fileNotFound); + } catch (IOException e) { + error = "" + e; + } catch (PGPException e) { + error = "" + e; + } catch (Apg.GeneralException e) { + error = "" + e; + } + + mImportData = null; + + if (mTask == Id.task.import_keys) { + data.putInt(Apg.EXTRA_STATUS, Id.message.import_done); + } else { + data.putInt(Apg.EXTRA_STATUS, Id.message.export_done); + } + + if (error != null) { + data.putString(Apg.EXTRA_ERROR, error); + } + + msg.setData(data); + sendMessage(msg); + } + + protected void deleteKey(int keyRingId) { + Apg.deleteKey(keyRingId); + refreshList(); + } + + protected void refreshList() { + mListAdapter.rebuild(true); + mListAdapter.notifyDataSetChanged(); + } + + @Override + public void doneCallback(Message msg) { + super.doneCallback(msg); + + Bundle data = msg.getData(); + if (data != null) { + int type = data.getInt(Apg.EXTRA_STATUS); + switch (type) { + case Id.message.import_done: { + removeDialog(Id.dialog.importing); + + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(KeyListActivity.this, + getString(R.string.errorMessage, error), + Toast.LENGTH_SHORT).show(); + } else { + int added = data.getInt("added"); + int updated = data.getInt("updated"); + String message; + if (added > 0 && updated > 0) { + message = getString(R.string.keysAddedAndUpdated, added, updated); + } else if (added > 0) { + message = getString(R.string.keysAdded, added); + } else if (updated > 0) { + message = getString(R.string.keysUpdated, updated); + } else { + message = getString(R.string.noKeysAddedOrUpdated); + } + Toast.makeText(KeyListActivity.this, message, + Toast.LENGTH_SHORT).show(); + } + refreshList(); + break; + } + + case Id.message.export_done: { + removeDialog(Id.dialog.exporting); + + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(KeyListActivity.this, + getString(R.string.errorMessage, error), + Toast.LENGTH_SHORT).show(); + } else { + int exported = data.getInt("exported"); + String message; + if (exported == 1) { + message = getString(R.string.keyExported); + } else if (exported > 0) { + message = getString(R.string.keysExported, exported); + } else{ + message = getString(R.string.noKeysExported); + } + Toast.makeText(KeyListActivity.this, message, + Toast.LENGTH_SHORT).show(); + } + break; + } + + default: { + break; + } + } + } + } + + protected class KeyListAdapter extends BaseExpandableListAdapter { + private LayoutInflater mInflater; + private Vector> mChildren; + private SQLiteDatabase mDatabase; + private Cursor mCursor; + private String mSearchString; + + private class KeyChild { + public static final int KEY = 0; + public static final int USER_ID = 1; + + public int type; + public String userId; + public long keyId; + public boolean isMasterKey; + public int algorithm; + public int keySize; + public boolean canSign; + public boolean canEncrypt; + + public KeyChild(long keyId, boolean isMasterKey, int algorithm, int keySize, + boolean canSign, boolean canEncrypt) { + this.keyId = keyId; + this.isMasterKey = isMasterKey; + this.algorithm = algorithm; + this.keySize = keySize; + this.canSign = canSign; + this.canEncrypt = canEncrypt; + } + + public KeyChild(String userId) { + type = USER_ID; + this.userId = userId; + } + } + + public KeyListAdapter(Context context, String searchString) { + mSearchString = searchString; + + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mDatabase = Apg.getDatabase().db(); + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + + "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + + ") " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + + "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0')"); + + if (searchString != null && searchString.trim().length() > 0) { + String[] chunks = searchString.trim().split(" +"); + qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + + UserIds.TABLE_NAME + " AS tmp WHERE " + + "tmp." + UserIds.KEY_ID + " = " + + Keys.TABLE_NAME + "." + Keys._ID); + for (int i = 0; i < chunks.length; ++i) { + qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); + qb.appendWhereEscapeString("%" + chunks[i] + "%"); + } + qb.appendWhere(")"); + } + + mCursor = qb.query(mDatabase, + new String[] { + KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 + UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 + }, + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", + new String[] { "" + (mKeyType == Id.type.public_key ? + Id.database.type_public : Id.database.type_secret) }, + null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"); + + // content provider way for reference, might have to go back to it sometime: + /*Uri contentUri = null; + if (mKeyType == Id.type.secret_key) { + contentUri = Apg.CONTENT_URI_SECRET_KEY_RINGS; + } else { + contentUri = Apg.CONTENT_URI_PUBLIC_KEY_RINGS; + } + mCursor = getContentResolver().query( + contentUri, + new String[] { + DataProvider._ID, // 0 + DataProvider.MASTER_KEY_ID, // 1 + DataProvider.USER_ID, // 2 + }, + null, null, null);*/ + + startManagingCursor(mCursor); + rebuild(false); + } + + public void cleanup() { + if (mCursor != null) { + stopManagingCursor(mCursor); + mCursor.close(); + } + } + + public void rebuild(boolean requery) { + if (requery) { + mCursor.requery(); + } + mChildren = new Vector>(); + for (int i = 0; i < mCursor.getCount(); ++i) { + mChildren.add(null); + } + } + + protected Vector getChildrenOfGroup(int groupPosition) { + Vector children = mChildren.get(groupPosition); + if (children != null) { + return children; + } + + mCursor.moveToPosition(groupPosition); + children = new Vector(); + Cursor c = mDatabase.query(Keys.TABLE_NAME, + new String[] { + Keys._ID, // 0 + Keys.KEY_ID, // 1 + Keys.IS_MASTER_KEY, // 2 + Keys.ALGORITHM, // 3 + Keys.KEY_SIZE, // 4 + Keys.CAN_SIGN, // 5 + Keys.CAN_ENCRYPT, // 6 + }, + Keys.KEY_RING_ID + " = ?", + new String[] { mCursor.getString(0) }, + null, null, Keys.RANK + " ASC"); + + long masterKeyId = -1; + for (int i = 0; i < c.getCount(); ++i) { + c.moveToPosition(i); + children.add(new KeyChild(c.getLong(1), c.getInt(2) == 1, c.getInt(3), c.getInt(4), + c.getInt(5) == 1, c.getInt(6) == 1)); + if (i == 0) { + masterKeyId = c.getInt(0); + } + } + c.close(); + + if (masterKeyId != -1) { + c = mDatabase.query(UserIds.TABLE_NAME, + new String[] { + UserIds.USER_ID, // 0 + }, + UserIds.KEY_ID + " = ? AND " + UserIds.RANK + " > 0", + new String[] { "" + masterKeyId }, + null, null, UserIds.RANK + " ASC"); + + for (int i = 0; i < c.getCount(); ++i) { + c.moveToPosition(i); + children.add(new KeyChild(c.getString(0))); + } + c.close(); + } + + mChildren.set(groupPosition, children); + return children; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + public int getGroupCount() { + return mCursor.getCount(); + } + + public Object getChild(int groupPosition, int childPosition) { + return null; + } + + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + public int getChildrenCount(int groupPosition) { + return getChildrenOfGroup(groupPosition).size(); + } + + public Object getGroup(int position) { + return position; + } + + public long getGroupId(int position) { + mCursor.moveToPosition(position); + return mCursor.getLong(1); // MASTER_KEY_ID + } + + public int getKeyRingId(int position) { + mCursor.moveToPosition(position); + return mCursor.getInt(0); // _ID + } + + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, + ViewGroup parent) { + mCursor.moveToPosition(groupPosition); + + View view = mInflater.inflate(R.layout.key_list_group_item, null); + view.setBackgroundResource(android.R.drawable.list_selector_background); + + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + mainUserId.setText(""); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mainUserIdRest.setText(""); + + String userId = mCursor.getString(2); // USER_ID + if (userId != null) { + String chunks[] = userId.split(" <", 2); + userId = chunks[0]; + if (chunks.length > 1) { + mainUserIdRest.setText("<" + chunks[1]); + } + mainUserId.setText(userId); + } + + if (mainUserId.getText().length() == 0) { + mainUserId.setText(R.string.unknownUserId); + } + + if (mainUserIdRest.getText().length() == 0) { + mainUserIdRest.setVisibility(View.GONE); + } + return view; + } + + public View getChildView(int groupPosition, int childPosition, + boolean isLastChild, View convertView, + ViewGroup parent) { + mCursor.moveToPosition(groupPosition); + + Vector children = getChildrenOfGroup(groupPosition); + + KeyChild child = children.get(childPosition); + View view = null; + switch (child.type) { + case KeyChild.KEY: { + if (child.isMasterKey) { + view = mInflater.inflate(R.layout.key_list_child_item_master_key, null); + } else { + view = mInflater.inflate(R.layout.key_list_child_item_sub_key, null); + } + + TextView keyId = (TextView) view.findViewById(R.id.keyId); + String keyIdStr = Long.toHexString(child.keyId & 0xffffffffL); + while (keyIdStr.length() < 8) { + keyIdStr = "0" + keyIdStr; + } + keyId.setText(keyIdStr); + TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails); + String algorithmStr = Apg.getAlgorithmInfo(child.algorithm, child.keySize); + keyDetails.setText("(" + algorithmStr + ")"); + + ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); + if (!child.canEncrypt) { + encryptIcon.setVisibility(View.GONE); + } + + ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); + if (!child.canSign) { + signIcon.setVisibility(View.GONE); + } + break; + } + + case KeyChild.USER_ID: { + view = mInflater.inflate(R.layout.key_list_child_item_user_id, null); + TextView userId = (TextView) view.findViewById(R.id.userId); + userId.setText(child.userId); + break; + } + } + return view; + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.filename: { + if (resultCode == RESULT_OK && data != null) { + String filename = data.getDataString(); + if (filename != null) { + // Get rid of URI prefix: + if (filename.startsWith("file://")) { + filename = filename.substring(7); + } + // replace %20 and so on + filename = Uri.decode(filename); + + FileDialog.setFilename(filename); + } + } + return; + } + + default: { + break; + } + } + super.onActivityResult(requestCode, resultCode, data); + } +} diff --git a/src/org/thialfihar/android/apg/MailListActivity.java b/src/org/thialfihar/android/apg/MailListActivity.java index d78dd47e1..dd856fc90 100644 --- a/src/org/thialfihar/android/apg/MailListActivity.java +++ b/src/org/thialfihar/android/apg/MailListActivity.java @@ -1,218 +1,218 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import java.util.Vector; -import java.util.regex.Matcher; - -import android.app.ListActivity; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.TextView; - -public class MailListActivity extends ListActivity { - LayoutInflater mInflater = null; - - private static class Conversation { - public long id; - public String subject; - public Vector messages; - - public Conversation(long id, String subject) { - this.id = id; - this.subject = subject; - } - } - - private static class Message { - public Conversation parent; - public long id; - public String subject; - public String fromAddress; - public String data; - public String replyTo; - public boolean signedOnly; - - public Message(Conversation parent, long id, String subject, - String fromAddress, String replyTo, - String data, boolean signedOnly) { - this.parent = parent; - this.id = id; - this.subject = subject; - this.fromAddress = fromAddress; - this.replyTo = replyTo; - this.data = data; - if (this.replyTo == null || this.replyTo.equals("")) { - this.replyTo = this.fromAddress; - } - this.signedOnly = signedOnly; - } - } - - private Vector mconversations; - private Vector mmessages; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - mconversations = new Vector(); - mmessages = new Vector(); - - String account = getIntent().getExtras().getString(Apg.EXTRA_ACCOUNT); - // TODO: what if account is null? - Uri uri = Uri.parse("content://gmail-ls/conversations/" + account); - Cursor cursor = - managedQuery(uri, new String[] { "conversation_id", "subject" }, null, null, null); - for (int i = 0; i < cursor.getCount(); ++i) { - cursor.moveToPosition(i); - - int idIndex = cursor.getColumnIndex("conversation_id"); - int subjectIndex = cursor.getColumnIndex("subject"); - long conversationId = cursor.getLong(idIndex); - Conversation conversation = - new Conversation(conversationId, cursor.getString(subjectIndex)); - Uri messageUri = Uri.withAppendedPath(uri, "" + conversationId + "/messages"); - Cursor messageCursor = - managedQuery(messageUri, new String[] { - "messageId", - "subject", - "fromAddress", - "replyToAddresses", - "body" }, null, null, null); - Vector messages = new Vector(); - for (int j = 0; j < messageCursor.getCount(); ++j) { - messageCursor.moveToPosition(j); - idIndex = messageCursor.getColumnIndex("messageId"); - subjectIndex = messageCursor.getColumnIndex("subject"); - int fromAddressIndex = messageCursor.getColumnIndex("fromAddress"); - int replyToIndex = messageCursor.getColumnIndex("replyToAddresses"); - int bodyIndex = messageCursor.getColumnIndex("body"); - String data = messageCursor.getString(bodyIndex); - data = Html.fromHtml(data).toString(); - boolean signedOnly = false; - Matcher matcher = Apg.PGP_MESSAGE.matcher(data); - if (matcher.matches()) { - data = matcher.group(1); - } else { - matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data); - if (matcher.matches()) { - data = matcher.group(1); - signedOnly = true; - } else { - data = null; - } - } - Message message = - new Message(conversation, - messageCursor.getLong(idIndex), - messageCursor.getString(subjectIndex), - messageCursor.getString(fromAddressIndex), - messageCursor.getString(replyToIndex), - data, signedOnly); - - messages.add(message); - mmessages.add(message); - } - conversation.messages = messages; - mconversations.add(conversation); - } - - setListAdapter(new MailboxAdapter()); - getListView().setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView arg0, View v, int position, long id) { - Intent intent = new Intent(MailListActivity.this, DecryptActivity.class); - intent.setAction(Apg.Intent.DECRYPT); - Message message = (Message) ((MailboxAdapter) getListAdapter()).getItem(position); - intent.putExtra(Apg.EXTRA_TEXT, message.data); - intent.putExtra(Apg.EXTRA_SUBJECT, message.subject); - intent.putExtra(Apg.EXTRA_REPLY_TO, message.replyTo); - startActivity(intent); - } - }); - } - - private class MailboxAdapter extends BaseAdapter implements ListAdapter { - - @Override - public boolean isEnabled(int position) { - Message message = (Message) getItem(position); - return message.data != null; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public int getCount() { - return mmessages.size(); - } - - @Override - public Object getItem(int position) { - return mmessages.get(position); - } - - @Override - public long getItemId(int position) { - return mmessages.get(position).id; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View view = mInflater.inflate(R.layout.mailbox_message_item, null); - - Message message = (Message) getItem(position); - - TextView subject = (TextView) view.findViewById(R.id.subject); - TextView email = (TextView) view.findViewById(R.id.emailAddress); - ImageView status = (ImageView) view.findViewById(R.id.ic_status); - - subject.setText(message.subject); - email.setText(message.fromAddress); - if (message.data != null) { - if (message.signedOnly) { - status.setImageResource(R.drawable.signed); - } else { - status.setImageResource(R.drawable.encrypted); - } - status.setVisibility(View.VISIBLE); - } else { - status.setVisibility(View.INVISIBLE); - } - - return view; - } - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import java.util.Vector; +import java.util.regex.Matcher; + +import android.app.ListActivity; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +public class MailListActivity extends ListActivity { + LayoutInflater mInflater = null; + + private static class Conversation { + public long id; + public String subject; + public Vector messages; + + public Conversation(long id, String subject) { + this.id = id; + this.subject = subject; + } + } + + private static class Message { + public Conversation parent; + public long id; + public String subject; + public String fromAddress; + public String data; + public String replyTo; + public boolean signedOnly; + + public Message(Conversation parent, long id, String subject, + String fromAddress, String replyTo, + String data, boolean signedOnly) { + this.parent = parent; + this.id = id; + this.subject = subject; + this.fromAddress = fromAddress; + this.replyTo = replyTo; + this.data = data; + if (this.replyTo == null || this.replyTo.equals("")) { + this.replyTo = this.fromAddress; + } + this.signedOnly = signedOnly; + } + } + + private Vector mconversations; + private Vector mmessages; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + mconversations = new Vector(); + mmessages = new Vector(); + + String account = getIntent().getExtras().getString(Apg.EXTRA_ACCOUNT); + // TODO: what if account is null? + Uri uri = Uri.parse("content://gmail-ls/conversations/" + account); + Cursor cursor = + managedQuery(uri, new String[] { "conversation_id", "subject" }, null, null, null); + for (int i = 0; i < cursor.getCount(); ++i) { + cursor.moveToPosition(i); + + int idIndex = cursor.getColumnIndex("conversation_id"); + int subjectIndex = cursor.getColumnIndex("subject"); + long conversationId = cursor.getLong(idIndex); + Conversation conversation = + new Conversation(conversationId, cursor.getString(subjectIndex)); + Uri messageUri = Uri.withAppendedPath(uri, "" + conversationId + "/messages"); + Cursor messageCursor = + managedQuery(messageUri, new String[] { + "messageId", + "subject", + "fromAddress", + "replyToAddresses", + "body" }, null, null, null); + Vector messages = new Vector(); + for (int j = 0; j < messageCursor.getCount(); ++j) { + messageCursor.moveToPosition(j); + idIndex = messageCursor.getColumnIndex("messageId"); + subjectIndex = messageCursor.getColumnIndex("subject"); + int fromAddressIndex = messageCursor.getColumnIndex("fromAddress"); + int replyToIndex = messageCursor.getColumnIndex("replyToAddresses"); + int bodyIndex = messageCursor.getColumnIndex("body"); + String data = messageCursor.getString(bodyIndex); + data = Html.fromHtml(data).toString(); + boolean signedOnly = false; + Matcher matcher = Apg.PGP_MESSAGE.matcher(data); + if (matcher.matches()) { + data = matcher.group(1); + } else { + matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data); + if (matcher.matches()) { + data = matcher.group(1); + signedOnly = true; + } else { + data = null; + } + } + Message message = + new Message(conversation, + messageCursor.getLong(idIndex), + messageCursor.getString(subjectIndex), + messageCursor.getString(fromAddressIndex), + messageCursor.getString(replyToIndex), + data, signedOnly); + + messages.add(message); + mmessages.add(message); + } + conversation.messages = messages; + mconversations.add(conversation); + } + + setListAdapter(new MailboxAdapter()); + getListView().setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView arg0, View v, int position, long id) { + Intent intent = new Intent(MailListActivity.this, DecryptActivity.class); + intent.setAction(Apg.Intent.DECRYPT); + Message message = (Message) ((MailboxAdapter) getListAdapter()).getItem(position); + intent.putExtra(Apg.EXTRA_TEXT, message.data); + intent.putExtra(Apg.EXTRA_SUBJECT, message.subject); + intent.putExtra(Apg.EXTRA_REPLY_TO, message.replyTo); + startActivity(intent); + } + }); + } + + private class MailboxAdapter extends BaseAdapter implements ListAdapter { + + @Override + public boolean isEnabled(int position) { + Message message = (Message) getItem(position); + return message.data != null; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public int getCount() { + return mmessages.size(); + } + + @Override + public Object getItem(int position) { + return mmessages.get(position); + } + + @Override + public long getItemId(int position) { + return mmessages.get(position).id; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = mInflater.inflate(R.layout.mailbox_message_item, null); + + Message message = (Message) getItem(position); + + TextView subject = (TextView) view.findViewById(R.id.subject); + TextView email = (TextView) view.findViewById(R.id.emailAddress); + ImageView status = (ImageView) view.findViewById(R.id.ic_status); + + subject.setText(message.subject); + email.setText(message.fromAddress); + if (message.data != null) { + if (message.signedOnly) { + status.setImageResource(R.drawable.signed); + } else { + status.setImageResource(R.drawable.encrypted); + } + status.setVisibility(View.VISIBLE); + } else { + status.setVisibility(View.INVISIBLE); + } + + return view; + } + } +} diff --git a/src/org/thialfihar/android/apg/PositionAwareInputStream.java b/src/org/thialfihar/android/apg/PositionAwareInputStream.java index 661e053f2..3b0fdc1f9 100644 --- a/src/org/thialfihar/android/apg/PositionAwareInputStream.java +++ b/src/org/thialfihar/android/apg/PositionAwareInputStream.java @@ -1,67 +1,67 @@ -package org.thialfihar.android.apg; - -import java.io.IOException; -import java.io.InputStream; - -public class PositionAwareInputStream extends InputStream { - private InputStream mStream; - private long mPosition; - - public PositionAwareInputStream(InputStream in) { - mStream = in; - mPosition = 0; - } - - @Override - public int read() throws IOException { - int ch = mStream.read(); - ++mPosition; - return ch; - } - - @Override - public int available() throws IOException { - return mStream.available(); - } - - @Override - public void close() throws IOException { - mStream.close(); - } - - @Override - public boolean markSupported() { - return false; - } - - @Override - public int read(byte[] b) throws IOException { - int result = mStream.read(b); - mPosition += result; - return result; - } - - @Override - public int read(byte[] b, int offset, int length) throws IOException { - int result = mStream.read(b, offset, length); - mPosition += result; - return result; - } - - @Override - public synchronized void reset() throws IOException { - mStream.reset(); - mPosition = 0; - } - - @Override - public long skip(long n) throws IOException { - long result = mStream.skip(n); - mPosition += result; - return result; - } - - public long position() { - return mPosition; - } -} +package org.thialfihar.android.apg; + +import java.io.IOException; +import java.io.InputStream; + +public class PositionAwareInputStream extends InputStream { + private InputStream mStream; + private long mPosition; + + public PositionAwareInputStream(InputStream in) { + mStream = in; + mPosition = 0; + } + + @Override + public int read() throws IOException { + int ch = mStream.read(); + ++mPosition; + return ch; + } + + @Override + public int available() throws IOException { + return mStream.available(); + } + + @Override + public void close() throws IOException { + mStream.close(); + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public int read(byte[] b) throws IOException { + int result = mStream.read(b); + mPosition += result; + return result; + } + + @Override + public int read(byte[] b, int offset, int length) throws IOException { + int result = mStream.read(b, offset, length); + mPosition += result; + return result; + } + + @Override + public synchronized void reset() throws IOException { + mStream.reset(); + mPosition = 0; + } + + @Override + public long skip(long n) throws IOException { + long result = mStream.skip(n); + mPosition += result; + return result; + } + + public long position() { + return mPosition; + } +} diff --git a/src/org/thialfihar/android/apg/PreferencesActivity.java b/src/org/thialfihar/android/apg/PreferencesActivity.java index 7e584a7a6..c8778c780 100644 --- a/src/org/thialfihar/android/apg/PreferencesActivity.java +++ b/src/org/thialfihar/android/apg/PreferencesActivity.java @@ -1,180 +1,180 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import org.bouncycastle2.bcpg.HashAlgorithmTags; -import org.bouncycastle2.openpgp.PGPEncryptedData; - -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.Preference; -import android.preference.PreferenceActivity; - -public class PreferencesActivity extends PreferenceActivity { - private IntegerListPreference mPassPhraseCacheTtl = null; - private IntegerListPreference mEncryptionAlgorithm = null; - private IntegerListPreference mHashAlgorithm = null; - private IntegerListPreference mMessageCompression = null; - private IntegerListPreference mFileCompression = null; - private CheckBoxPreference mAsciiArmour = null; - private Preferences mPreferences; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mPreferences = Preferences.getPreferences(this); - - addPreferencesFromResource(R.xml.apg_preferences); - - mPassPhraseCacheTtl = (IntegerListPreference) findPreference(Constants.pref.pass_phrase_cache_ttl); - mPassPhraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl()); - mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry()); - mPassPhraseCacheTtl.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() - { - public boolean onPreferenceChange(Preference preference, Object newValue) - { - mPassPhraseCacheTtl.setValue(newValue.toString()); - mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry()); - mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString())); - BaseActivity.startCacheService(PreferencesActivity.this, mPreferences); - return false; - } - }); - - mEncryptionAlgorithm = (IntegerListPreference) findPreference(Constants.pref.default_encryption_algorithm); - int valueIds[] = { - PGPEncryptedData.AES_128, PGPEncryptedData.AES_192, PGPEncryptedData.AES_256, - PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH, PGPEncryptedData.CAST5, - PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES, PGPEncryptedData.IDEA, - }; - String entries[] = { - "AES-128", "AES-192", "AES-256", - "Blowfish", "Twofish", "CAST5", - "DES", "Triple DES", "IDEA", - }; - String values[] = new String[valueIds.length]; - for (int i = 0; i < values.length; ++i) { - values[i] = "" + valueIds[i]; - } - mEncryptionAlgorithm.setEntries(entries); - mEncryptionAlgorithm.setEntryValues(values); - mEncryptionAlgorithm.setValue("" + mPreferences.getDefaultEncryptionAlgorithm()); - mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); - mEncryptionAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() - { - public boolean onPreferenceChange(Preference preference, Object newValue) - { - mEncryptionAlgorithm.setValue(newValue.toString()); - mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); - mPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue.toString())); - return false; - } - }); - - mHashAlgorithm = (IntegerListPreference) findPreference(Constants.pref.default_hash_algorithm); - valueIds = new int[] { - HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160, HashAlgorithmTags.SHA1, - HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA384, - HashAlgorithmTags.SHA512, - }; - entries = new String[] { - "MD5", "RIPEMD-160", "SHA-1", - "SHA-224", "SHA-256", "SHA-384", - "SHA-512", - }; - values = new String[valueIds.length]; - for (int i = 0; i < values.length; ++i) { - values[i] = "" + valueIds[i]; - } - mHashAlgorithm.setEntries(entries); - mHashAlgorithm.setEntryValues(values); - mHashAlgorithm.setValue("" + mPreferences.getDefaultHashAlgorithm()); - mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); - mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() - { - public boolean onPreferenceChange(Preference preference, Object newValue) - { - mHashAlgorithm.setValue(newValue.toString()); - mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); - mPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString())); - return false; - } - }); - - mMessageCompression = (IntegerListPreference) findPreference(Constants.pref.default_message_compression); - valueIds = new int[] { - Id.choice.compression.none, - Id.choice.compression.zip, - Id.choice.compression.zlib, - Id.choice.compression.bzip2, - }; - entries = new String[] { - getString(R.string.choice_none) + " (" + getString(R.string.fast) + ")", - "ZIP (" + getString(R.string.fast) + ")", - "ZLIB (" + getString(R.string.fast) + ")", - "BZIP2 (" + getString(R.string.very_slow) + ")", - }; - values = new String[valueIds.length]; - for (int i = 0; i < values.length; ++i) { - values[i] = "" + valueIds[i]; - } - mMessageCompression.setEntries(entries); - mMessageCompression.setEntryValues(values); - mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression()); - mMessageCompression.setSummary(mMessageCompression.getEntry()); - mMessageCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() - { - public boolean onPreferenceChange(Preference preference, Object newValue) - { - mMessageCompression.setValue(newValue.toString()); - mMessageCompression.setSummary(mMessageCompression.getEntry()); - mPreferences.setDefaultMessageCompression(Integer.parseInt(newValue.toString())); - return false; - } - }); - - mFileCompression = (IntegerListPreference) findPreference(Constants.pref.default_file_compression); - mFileCompression.setEntries(entries); - mFileCompression.setEntryValues(values); - mFileCompression.setValue("" + mPreferences.getDefaultFileCompression()); - mFileCompression.setSummary(mFileCompression.getEntry()); - mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() - { - public boolean onPreferenceChange(Preference preference, Object newValue) - { - mFileCompression.setValue(newValue.toString()); - mFileCompression.setSummary(mFileCompression.getEntry()); - mPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString())); - return false; - } - }); - - mAsciiArmour = (CheckBoxPreference) findPreference(Constants.pref.default_ascii_armour); - mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour()); - mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() - { - public boolean onPreferenceChange(Preference preference, Object newValue) - { - mAsciiArmour.setChecked((Boolean)newValue); - mPreferences.setDefaultAsciiArmour((Boolean)newValue); - return false; - } - }); - } -} - +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import org.bouncycastle2.bcpg.HashAlgorithmTags; +import org.bouncycastle2.openpgp.PGPEncryptedData; + +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; + +public class PreferencesActivity extends PreferenceActivity { + private IntegerListPreference mPassPhraseCacheTtl = null; + private IntegerListPreference mEncryptionAlgorithm = null; + private IntegerListPreference mHashAlgorithm = null; + private IntegerListPreference mMessageCompression = null; + private IntegerListPreference mFileCompression = null; + private CheckBoxPreference mAsciiArmour = null; + private Preferences mPreferences; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mPreferences = Preferences.getPreferences(this); + + addPreferencesFromResource(R.xml.apg_preferences); + + mPassPhraseCacheTtl = (IntegerListPreference) findPreference(Constants.pref.pass_phrase_cache_ttl); + mPassPhraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl()); + mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry()); + mPassPhraseCacheTtl.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() + { + public boolean onPreferenceChange(Preference preference, Object newValue) + { + mPassPhraseCacheTtl.setValue(newValue.toString()); + mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry()); + mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString())); + BaseActivity.startCacheService(PreferencesActivity.this, mPreferences); + return false; + } + }); + + mEncryptionAlgorithm = (IntegerListPreference) findPreference(Constants.pref.default_encryption_algorithm); + int valueIds[] = { + PGPEncryptedData.AES_128, PGPEncryptedData.AES_192, PGPEncryptedData.AES_256, + PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH, PGPEncryptedData.CAST5, + PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES, PGPEncryptedData.IDEA, + }; + String entries[] = { + "AES-128", "AES-192", "AES-256", + "Blowfish", "Twofish", "CAST5", + "DES", "Triple DES", "IDEA", + }; + String values[] = new String[valueIds.length]; + for (int i = 0; i < values.length; ++i) { + values[i] = "" + valueIds[i]; + } + mEncryptionAlgorithm.setEntries(entries); + mEncryptionAlgorithm.setEntryValues(values); + mEncryptionAlgorithm.setValue("" + mPreferences.getDefaultEncryptionAlgorithm()); + mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); + mEncryptionAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() + { + public boolean onPreferenceChange(Preference preference, Object newValue) + { + mEncryptionAlgorithm.setValue(newValue.toString()); + mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); + mPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue.toString())); + return false; + } + }); + + mHashAlgorithm = (IntegerListPreference) findPreference(Constants.pref.default_hash_algorithm); + valueIds = new int[] { + HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160, HashAlgorithmTags.SHA1, + HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA384, + HashAlgorithmTags.SHA512, + }; + entries = new String[] { + "MD5", "RIPEMD-160", "SHA-1", + "SHA-224", "SHA-256", "SHA-384", + "SHA-512", + }; + values = new String[valueIds.length]; + for (int i = 0; i < values.length; ++i) { + values[i] = "" + valueIds[i]; + } + mHashAlgorithm.setEntries(entries); + mHashAlgorithm.setEntryValues(values); + mHashAlgorithm.setValue("" + mPreferences.getDefaultHashAlgorithm()); + mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); + mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() + { + public boolean onPreferenceChange(Preference preference, Object newValue) + { + mHashAlgorithm.setValue(newValue.toString()); + mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); + mPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString())); + return false; + } + }); + + mMessageCompression = (IntegerListPreference) findPreference(Constants.pref.default_message_compression); + valueIds = new int[] { + Id.choice.compression.none, + Id.choice.compression.zip, + Id.choice.compression.zlib, + Id.choice.compression.bzip2, + }; + entries = new String[] { + getString(R.string.choice_none) + " (" + getString(R.string.fast) + ")", + "ZIP (" + getString(R.string.fast) + ")", + "ZLIB (" + getString(R.string.fast) + ")", + "BZIP2 (" + getString(R.string.very_slow) + ")", + }; + values = new String[valueIds.length]; + for (int i = 0; i < values.length; ++i) { + values[i] = "" + valueIds[i]; + } + mMessageCompression.setEntries(entries); + mMessageCompression.setEntryValues(values); + mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression()); + mMessageCompression.setSummary(mMessageCompression.getEntry()); + mMessageCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() + { + public boolean onPreferenceChange(Preference preference, Object newValue) + { + mMessageCompression.setValue(newValue.toString()); + mMessageCompression.setSummary(mMessageCompression.getEntry()); + mPreferences.setDefaultMessageCompression(Integer.parseInt(newValue.toString())); + return false; + } + }); + + mFileCompression = (IntegerListPreference) findPreference(Constants.pref.default_file_compression); + mFileCompression.setEntries(entries); + mFileCompression.setEntryValues(values); + mFileCompression.setValue("" + mPreferences.getDefaultFileCompression()); + mFileCompression.setSummary(mFileCompression.getEntry()); + mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() + { + public boolean onPreferenceChange(Preference preference, Object newValue) + { + mFileCompression.setValue(newValue.toString()); + mFileCompression.setSummary(mFileCompression.getEntry()); + mPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString())); + return false; + } + }); + + mAsciiArmour = (CheckBoxPreference) findPreference(Constants.pref.default_ascii_armour); + mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour()); + mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() + { + public boolean onPreferenceChange(Preference preference, Object newValue) + { + mAsciiArmour.setChecked((Boolean)newValue); + mPreferences.setDefaultAsciiArmour((Boolean)newValue); + return false; + } + }); + } +} + diff --git a/src/org/thialfihar/android/apg/Primes.java b/src/org/thialfihar/android/apg/Primes.java index e97a6c6c5..f0f391291 100644 --- a/src/org/thialfihar/android/apg/Primes.java +++ b/src/org/thialfihar/android/apg/Primes.java @@ -1,185 +1,185 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import java.math.BigInteger; - -public final class Primes { - // taken from http://www.ietf.org/rfc/rfc3526.txt - public static final String P1536 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF"; - - public static final String P2048 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + - "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + - "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + - "15728E5A 8AACAA68 FFFFFFFF FFFFFFFF"; - - public static final String P3072 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + - "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + - "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + - "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + - "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + - "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + - "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + - "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + - "43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF"; - - public static final String P4096 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + - "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + - "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + - "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + - "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + - "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + - "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + - "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + - "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + - "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + - "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + - "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + - "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + - "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199" + - "FFFFFFFF FFFFFFFF"; - - public static final String P6144 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + - "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + - "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + - "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + - "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + - "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + - "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + - "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + - "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + - "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + - "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + - "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + - "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + - "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" + - "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" + - "F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" + - "179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" + - "DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" + - "5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" + - "D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" + - "23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" + - "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" + - "06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" + - "DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" + - "12BF2D5B 0B7474D6 E694F91E 6DCC4024 FFFFFFFF FFFFFFFF"; - - public static final String P8192 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + - "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + - "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + - "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + - "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + - "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + - "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + - "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + - "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + - "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + - "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + - "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + - "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + - "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" + - "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" + - "F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" + - "179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" + - "DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" + - "5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" + - "D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" + - "23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" + - "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" + - "06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" + - "DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" + - "12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4" + - "38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300" + - "741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568" + - "3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9" + - "22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B" + - "4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A" + - "062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36" + - "4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1" + - "B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92" + - "4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47" + - "9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71" + - "60C980DD 98EDD3DF FFFFFFFF FFFFFFFF"; - - public static BigInteger getBestPrime(int keySize) { - String primeString; - if (keySize >= (8192 + 6144) / 2) { - primeString = P8192; - } else if (keySize >= (6144 + 4096) / 2) { - primeString = P6144; - } else if (keySize >= (4096 + 3072) / 2) { - primeString = P4096; - } else if (keySize >= (3072 + 2048) / 2) { - primeString = P3072; - } else if (keySize >= (2048 + 1536) / 2) { - primeString = P2048; - } else { - primeString = P1536; - } - - return new BigInteger(primeString.replaceAll(" ", ""), 16); - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import java.math.BigInteger; + +public final class Primes { + // taken from http://www.ietf.org/rfc/rfc3526.txt + public static final String P1536 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF"; + + public static final String P2048 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + + "15728E5A 8AACAA68 FFFFFFFF FFFFFFFF"; + + public static final String P3072 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + + "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + + "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + + "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + + "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + + "43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF"; + + public static final String P4096 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + + "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + + "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + + "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + + "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + + "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + + "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + + "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + + "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + + "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + + "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199" + + "FFFFFFFF FFFFFFFF"; + + public static final String P6144 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + + "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + + "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + + "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + + "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + + "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + + "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + + "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + + "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + + "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + + "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" + + "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" + + "F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" + + "179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" + + "DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" + + "5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" + + "D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" + + "23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" + + "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" + + "06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" + + "DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" + + "12BF2D5B 0B7474D6 E694F91E 6DCC4024 FFFFFFFF FFFFFFFF"; + + public static final String P8192 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + + "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + + "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + + "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + + "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + + "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + + "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + + "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + + "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + + "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + + "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" + + "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" + + "F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" + + "179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" + + "DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" + + "5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" + + "D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" + + "23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" + + "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" + + "06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" + + "DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" + + "12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4" + + "38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300" + + "741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568" + + "3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9" + + "22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B" + + "4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A" + + "062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36" + + "4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1" + + "B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92" + + "4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47" + + "9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71" + + "60C980DD 98EDD3DF FFFFFFFF FFFFFFFF"; + + public static BigInteger getBestPrime(int keySize) { + String primeString; + if (keySize >= (8192 + 6144) / 2) { + primeString = P8192; + } else if (keySize >= (6144 + 4096) / 2) { + primeString = P6144; + } else if (keySize >= (4096 + 3072) / 2) { + primeString = P4096; + } else if (keySize >= (3072 + 2048) / 2) { + primeString = P3072; + } else if (keySize >= (2048 + 1536) / 2) { + primeString = P2048; + } else { + primeString = P1536; + } + + return new BigInteger(primeString.replaceAll(" ", ""), 16); + } +} diff --git a/src/org/thialfihar/android/apg/ProgressDialogUpdater.java b/src/org/thialfihar/android/apg/ProgressDialogUpdater.java index fe38c2a34..691a90353 100644 --- a/src/org/thialfihar/android/apg/ProgressDialogUpdater.java +++ b/src/org/thialfihar/android/apg/ProgressDialogUpdater.java @@ -1,23 +1,23 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -public interface ProgressDialogUpdater { - void setProgress(String message, int current, int total); - void setProgress(int resourceId, int current, int total); - void setProgress(int current, int total); -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +public interface ProgressDialogUpdater { + void setProgress(String message, int current, int total); + void setProgress(int resourceId, int current, int total); + void setProgress(int current, int total); +} diff --git a/src/org/thialfihar/android/apg/PublicKeyListActivity.java b/src/org/thialfihar/android/apg/PublicKeyListActivity.java index 916d4e6c2..610e23683 100644 --- a/src/org/thialfihar/android/apg/PublicKeyListActivity.java +++ b/src/org/thialfihar/android/apg/PublicKeyListActivity.java @@ -1,62 +1,62 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import android.os.Bundle; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.Menu; -import android.view.View; -import android.widget.ExpandableListView; - -public class PublicKeyListActivity extends KeyListActivity { - @Override - public void onCreate(Bundle savedInstanceState) { - mExportFilename = Constants.path.app_dir + "/pubexport.asc"; - mKeyType = Id.type.public_key; - super.onCreate(savedInstanceState); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, Id.menu.option.import_keys, 0, R.string.menu_importKeys) - .setIcon(android.R.drawable.ic_menu_add); - menu.add(0, Id.menu.option.export_keys, 1, R.string.menu_exportKeys) - .setIcon(android.R.drawable.ic_menu_save); - menu.add(1, Id.menu.option.search, 2, R.string.menu_search) - .setIcon(android.R.drawable.ic_menu_search); - menu.add(1, Id.menu.option.preferences, 3, R.string.menu_preferences) - .setIcon(android.R.drawable.ic_menu_preferences); - menu.add(1, Id.menu.option.about, 4, R.string.menu_about) - .setIcon(android.R.drawable.ic_menu_info_details); - return true; - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - ExpandableListView.ExpandableListContextMenuInfo info = - (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; - int type = ExpandableListView.getPackedPositionType(info.packedPosition); - - if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { - // TODO: user id? menu.setHeaderTitle("Key"); - menu.add(0, Id.menu.export, 0, R.string.menu_exportKey); - menu.add(0, Id.menu.delete, 1, R.string.menu_deleteKey); - } - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.View; +import android.widget.ExpandableListView; + +public class PublicKeyListActivity extends KeyListActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + mExportFilename = Constants.path.app_dir + "/pubexport.asc"; + mKeyType = Id.type.public_key; + super.onCreate(savedInstanceState); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, Id.menu.option.import_keys, 0, R.string.menu_importKeys) + .setIcon(android.R.drawable.ic_menu_add); + menu.add(0, Id.menu.option.export_keys, 1, R.string.menu_exportKeys) + .setIcon(android.R.drawable.ic_menu_save); + menu.add(1, Id.menu.option.search, 2, R.string.menu_search) + .setIcon(android.R.drawable.ic_menu_search); + menu.add(1, Id.menu.option.preferences, 3, R.string.menu_preferences) + .setIcon(android.R.drawable.ic_menu_preferences); + menu.add(1, Id.menu.option.about, 4, R.string.menu_about) + .setIcon(android.R.drawable.ic_menu_info_details); + return true; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + ExpandableListView.ExpandableListContextMenuInfo info = + (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; + int type = ExpandableListView.getPackedPositionType(info.packedPosition); + + if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + // TODO: user id? menu.setHeaderTitle("Key"); + menu.add(0, Id.menu.export, 0, R.string.menu_exportKey); + menu.add(0, Id.menu.delete, 1, R.string.menu_deleteKey); + } + } +} diff --git a/src/org/thialfihar/android/apg/SecretKeyListActivity.java b/src/org/thialfihar/android/apg/SecretKeyListActivity.java index 9ff7f0fa3..d3c89ddc9 100644 --- a/src/org/thialfihar/android/apg/SecretKeyListActivity.java +++ b/src/org/thialfihar/android/apg/SecretKeyListActivity.java @@ -1,180 +1,180 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import android.app.Dialog; -import android.content.Intent; -import android.os.Bundle; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.ExpandableListView; -import android.widget.ExpandableListView.ExpandableListContextMenuInfo; -import android.widget.ExpandableListView.OnChildClickListener; - -public class SecretKeyListActivity extends KeyListActivity implements OnChildClickListener { - @Override - public void onCreate(Bundle savedInstanceState) { - mExportFilename = Constants.path.app_dir + "/secexport.asc"; - mKeyType = Id.type.secret_key; - super.onCreate(savedInstanceState); - mList.setOnChildClickListener(this); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, Id.menu.option.import_keys, 0, R.string.menu_importKeys) - .setIcon(android.R.drawable.ic_menu_add); - menu.add(0, Id.menu.option.export_keys, 1, R.string.menu_exportKeys) - .setIcon(android.R.drawable.ic_menu_save); - menu.add(1, Id.menu.option.create, 2, R.string.menu_createKey) - .setIcon(android.R.drawable.ic_menu_add); - menu.add(3, Id.menu.option.search, 3, R.string.menu_search) - .setIcon(android.R.drawable.ic_menu_search); - menu.add(3, Id.menu.option.preferences, 4, R.string.menu_preferences) - .setIcon(android.R.drawable.ic_menu_preferences); - menu.add(3, Id.menu.option.about, 5, R.string.menu_about) - .setIcon(android.R.drawable.ic_menu_info_details); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case Id.menu.option.create: { - createKey(); - return true; - } - - default: { - return super.onOptionsItemSelected(item); - } - } - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - ExpandableListView.ExpandableListContextMenuInfo info = - (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; - int type = ExpandableListView.getPackedPositionType(info.packedPosition); - - if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { - // TODO: user id? menu.setHeaderTitle("Key"); - menu.add(0, Id.menu.edit, 0, R.string.menu_editKey); - menu.add(0, Id.menu.export, 1, R.string.menu_exportKey); - menu.add(0, Id.menu.delete, 2, R.string.menu_deleteKey); - } - } - - @Override - public boolean onContextItemSelected(MenuItem menuItem) { - ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); - int type = ExpandableListView.getPackedPositionType(info.packedPosition); - int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); - - if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) { - return super.onContextItemSelected(menuItem); - } - - switch (menuItem.getItemId()) { - case Id.menu.edit: { - mSelectedItem = groupPosition; - checkPassPhraseAndEdit(); - return true; - } - - default: { - return super.onContextItemSelected(menuItem); - } - } - } - - @Override - public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, - int childPosition, long id) { - mSelectedItem = groupPosition; - checkPassPhraseAndEdit(); - return true; - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case Id.dialog.pass_phrase: { - long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem); - return AskForSecretKeyPassPhrase.createDialog(this, keyId, this); - } - - default: { - return super.onCreateDialog(id); - } - } - } - - public void checkPassPhraseAndEdit() { - long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem); - String passPhrase = Apg.getCachedPassPhrase(keyId); - if (passPhrase == null) { - showDialog(Id.dialog.pass_phrase); - } else { - Apg.setEditPassPhrase(passPhrase); - editKey(); - } - } - - @Override - public void passPhraseCallback(long keyId, String passPhrase) { - super.passPhraseCallback(keyId, passPhrase); - Apg.setEditPassPhrase(passPhrase); - editKey(); - } - - private void createKey() { - Apg.setEditPassPhrase(""); - Intent intent = new Intent(this, EditKeyActivity.class); - startActivityForResult(intent, Id.message.create_key); - } - - private void editKey() { - long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem); - Intent intent = new Intent(this, EditKeyActivity.class); - intent.putExtra(Apg.EXTRA_KEY_ID, keyId); - startActivityForResult(intent, Id.message.edit_key); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.message.create_key: // intentionally no break - case Id.message.edit_key: { - if (resultCode == RESULT_OK) { - refreshList(); - } - break; - } - - default: { - break; - } - } - - super.onActivityResult(requestCode, resultCode, data); - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import android.app.Dialog; +import android.content.Intent; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ExpandableListView; +import android.widget.ExpandableListView.ExpandableListContextMenuInfo; +import android.widget.ExpandableListView.OnChildClickListener; + +public class SecretKeyListActivity extends KeyListActivity implements OnChildClickListener { + @Override + public void onCreate(Bundle savedInstanceState) { + mExportFilename = Constants.path.app_dir + "/secexport.asc"; + mKeyType = Id.type.secret_key; + super.onCreate(savedInstanceState); + mList.setOnChildClickListener(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, Id.menu.option.import_keys, 0, R.string.menu_importKeys) + .setIcon(android.R.drawable.ic_menu_add); + menu.add(0, Id.menu.option.export_keys, 1, R.string.menu_exportKeys) + .setIcon(android.R.drawable.ic_menu_save); + menu.add(1, Id.menu.option.create, 2, R.string.menu_createKey) + .setIcon(android.R.drawable.ic_menu_add); + menu.add(3, Id.menu.option.search, 3, R.string.menu_search) + .setIcon(android.R.drawable.ic_menu_search); + menu.add(3, Id.menu.option.preferences, 4, R.string.menu_preferences) + .setIcon(android.R.drawable.ic_menu_preferences); + menu.add(3, Id.menu.option.about, 5, R.string.menu_about) + .setIcon(android.R.drawable.ic_menu_info_details); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Id.menu.option.create: { + createKey(); + return true; + } + + default: { + return super.onOptionsItemSelected(item); + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + ExpandableListView.ExpandableListContextMenuInfo info = + (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; + int type = ExpandableListView.getPackedPositionType(info.packedPosition); + + if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + // TODO: user id? menu.setHeaderTitle("Key"); + menu.add(0, Id.menu.edit, 0, R.string.menu_editKey); + menu.add(0, Id.menu.export, 1, R.string.menu_exportKey); + menu.add(0, Id.menu.delete, 2, R.string.menu_deleteKey); + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); + int type = ExpandableListView.getPackedPositionType(info.packedPosition); + int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); + + if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + return super.onContextItemSelected(menuItem); + } + + switch (menuItem.getItemId()) { + case Id.menu.edit: { + mSelectedItem = groupPosition; + checkPassPhraseAndEdit(); + return true; + } + + default: { + return super.onContextItemSelected(menuItem); + } + } + } + + @Override + public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, + int childPosition, long id) { + mSelectedItem = groupPosition; + checkPassPhraseAndEdit(); + return true; + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case Id.dialog.pass_phrase: { + long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem); + return AskForSecretKeyPassPhrase.createDialog(this, keyId, this); + } + + default: { + return super.onCreateDialog(id); + } + } + } + + public void checkPassPhraseAndEdit() { + long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem); + String passPhrase = Apg.getCachedPassPhrase(keyId); + if (passPhrase == null) { + showDialog(Id.dialog.pass_phrase); + } else { + Apg.setEditPassPhrase(passPhrase); + editKey(); + } + } + + @Override + public void passPhraseCallback(long keyId, String passPhrase) { + super.passPhraseCallback(keyId, passPhrase); + Apg.setEditPassPhrase(passPhrase); + editKey(); + } + + private void createKey() { + Apg.setEditPassPhrase(""); + Intent intent = new Intent(this, EditKeyActivity.class); + startActivityForResult(intent, Id.message.create_key); + } + + private void editKey() { + long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem); + Intent intent = new Intent(this, EditKeyActivity.class); + intent.putExtra(Apg.EXTRA_KEY_ID, keyId); + startActivityForResult(intent, Id.message.edit_key); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.message.create_key: // intentionally no break + case Id.message.edit_key: { + if (resultCode == RESULT_OK) { + refreshList(); + } + break; + } + + default: { + break; + } + } + + super.onActivityResult(requestCode, resultCode, data); + } +} diff --git a/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java b/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java index 095772ec2..cbbb88371 100644 --- a/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java +++ b/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java @@ -1,225 +1,225 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg; - -import java.util.Date; - -import org.thialfihar.android.apg.provider.KeyRings; -import org.thialfihar.android.apg.provider.Keys; -import org.thialfihar.android.apg.provider.UserIds; - -import android.app.Activity; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.ListView; -import android.widget.TextView; - -public class SelectPublicKeyListAdapter extends BaseAdapter { - protected LayoutInflater mInflater; - protected ListView mParent; - protected SQLiteDatabase mDatabase; - protected Cursor mCursor; - protected String mSearchString; - protected Activity mActivity; - - public SelectPublicKeyListAdapter(Activity activity, ListView parent, - String searchString, long selectedKeyIds[]) { - mSearchString = searchString; - - mActivity = activity; - mParent = parent; - mDatabase = Apg.getDatabase().db(); - mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - long now = new Date().getTime() / 1000; - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + - "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + - Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + - ") " + - " INNER JOIN " + UserIds.TABLE_NAME + " ON " + - "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + - UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + - UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); - - String inIdList = null; - - if (selectedKeyIds != null && selectedKeyIds.length > 0) { - inIdList = KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " IN ("; - for (int i = 0; i < selectedKeyIds.length; ++i) { - if (i != 0) { - inIdList += ", "; - } - inIdList += DatabaseUtils.sqlEscapeString("" + selectedKeyIds[i]); - } - inIdList += ")"; - } - - if (searchString != null && searchString.trim().length() > 0) { - String[] chunks = searchString.trim().split(" +"); - qb.appendWhere("(EXISTS (SELECT tmp." + UserIds._ID + " FROM " + - UserIds.TABLE_NAME + " AS tmp WHERE " + - "tmp." + UserIds.KEY_ID + " = " + - Keys.TABLE_NAME + "." + Keys._ID); - for (int i = 0; i < chunks.length; ++i) { - qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); - qb.appendWhereEscapeString("%" + chunks[i] + "%"); - } - qb.appendWhere("))"); - - if (inIdList != null) { - qb.appendWhere(" OR (" + inIdList + ")"); - } - } - - String orderBy = UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"; - if (inIdList != null) { - orderBy = inIdList + " DESC, " + orderBy; - } - - mCursor = qb.query(mDatabase, - new String[] { - KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 - KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 - UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 - "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + - "tmp." + Keys.KEY_RING_ID + " = " + - KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " + - "tmp." + Keys.IS_REVOKED + " = '0' AND " + - "tmp." + Keys.CAN_ENCRYPT + " = '1')", // 3 - "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + - "tmp." + Keys.KEY_RING_ID + " = " + - KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " + - "tmp." + Keys.IS_REVOKED + " = '0' AND " + - "tmp." + Keys.CAN_ENCRYPT + " = '1' AND " + - "tmp." + Keys.CREATION + " <= '" + now + "' AND " + - "(tmp." + Keys.EXPIRY + " IS NULL OR " + - "tmp." + Keys.EXPIRY + " >= '" + now + "'))", // 4 - }, - KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", - new String[] { "" + Id.database.type_public }, - null, null, orderBy); - - activity.startManagingCursor(mCursor); - } - - public void cleanup() { - if (mCursor != null) { - mActivity.stopManagingCursor(mCursor); - mCursor.close(); - } - } - - @Override - public boolean isEnabled(int position) { - mCursor.moveToPosition(position); - return mCursor.getInt(4) > 0; // valid CAN_ENCRYPT - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public int getCount() { - return mCursor.getCount(); - } - - @Override - public Object getItem(int position) { - return position; - } - - @Override - public long getItemId(int position) { - mCursor.moveToPosition(position); - return mCursor.getLong(1); // MASTER_KEY_ID - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - mCursor.moveToPosition(position); - - View view = mInflater.inflate(R.layout.select_public_key_item, null); - boolean enabled = isEnabled(position); - - TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(R.string.unknownUserId); - TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); - TextView keyId = (TextView) view.findViewById(R.id.keyId); - keyId.setText(R.string.noKey); - TextView status = (TextView) view.findViewById(R.id.status); - status.setText(R.string.unknownStatus); - - String userId = mCursor.getString(2); // USER_ID - if (userId != null) { - String chunks[] = userId.split(" <", 2); - userId = chunks[0]; - if (chunks.length > 1) { - mainUserIdRest.setText("<" + chunks[1]); - } - mainUserId.setText(userId); - } - - long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID - keyId.setText("" + Long.toHexString(masterKeyId & 0xffffffffL)); - - if (mainUserIdRest.getText().length() == 0) { - mainUserIdRest.setVisibility(View.GONE); - } - - if (enabled) { - status.setText(R.string.canEncrypt); - } else { - if (mCursor.getInt(3) > 0) { - // has some CAN_ENCRYPT keys, but col(4) = 0, so must be revoked or expired - status.setText(R.string.expired); - } else { - status.setText(R.string.noKey); - } - } - - status.setText(status.getText() + " "); - - CheckBox selected = (CheckBox) view.findViewById(R.id.selected); - - if (!enabled) { - mParent.setItemChecked(position, false); - } - - selected.setChecked(mParent.isItemChecked(position)); - - view.setEnabled(enabled); - mainUserId.setEnabled(enabled); - mainUserIdRest.setEnabled(enabled); - keyId.setEnabled(enabled); - selected.setEnabled(enabled); - status.setEnabled(enabled); - - return view; - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg; + +import java.util.Date; + +import org.thialfihar.android.apg.provider.KeyRings; +import org.thialfihar.android.apg.provider.Keys; +import org.thialfihar.android.apg.provider.UserIds; + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.CheckBox; +import android.widget.ListView; +import android.widget.TextView; + +public class SelectPublicKeyListAdapter extends BaseAdapter { + protected LayoutInflater mInflater; + protected ListView mParent; + protected SQLiteDatabase mDatabase; + protected Cursor mCursor; + protected String mSearchString; + protected Activity mActivity; + + public SelectPublicKeyListAdapter(Activity activity, ListView parent, + String searchString, long selectedKeyIds[]) { + mSearchString = searchString; + + mActivity = activity; + mParent = parent; + mDatabase = Apg.getDatabase().db(); + mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + long now = new Date().getTime() / 1000; + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + + "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + + ") " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + + "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); + + String inIdList = null; + + if (selectedKeyIds != null && selectedKeyIds.length > 0) { + inIdList = KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " IN ("; + for (int i = 0; i < selectedKeyIds.length; ++i) { + if (i != 0) { + inIdList += ", "; + } + inIdList += DatabaseUtils.sqlEscapeString("" + selectedKeyIds[i]); + } + inIdList += ")"; + } + + if (searchString != null && searchString.trim().length() > 0) { + String[] chunks = searchString.trim().split(" +"); + qb.appendWhere("(EXISTS (SELECT tmp." + UserIds._ID + " FROM " + + UserIds.TABLE_NAME + " AS tmp WHERE " + + "tmp." + UserIds.KEY_ID + " = " + + Keys.TABLE_NAME + "." + Keys._ID); + for (int i = 0; i < chunks.length; ++i) { + qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); + qb.appendWhereEscapeString("%" + chunks[i] + "%"); + } + qb.appendWhere("))"); + + if (inIdList != null) { + qb.appendWhere(" OR (" + inIdList + ")"); + } + } + + String orderBy = UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"; + if (inIdList != null) { + orderBy = inIdList + " DESC, " + orderBy; + } + + mCursor = qb.query(mDatabase, + new String[] { + KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 + UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 + "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + + "tmp." + Keys.KEY_RING_ID + " = " + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " + + "tmp." + Keys.IS_REVOKED + " = '0' AND " + + "tmp." + Keys.CAN_ENCRYPT + " = '1')", // 3 + "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + + "tmp." + Keys.KEY_RING_ID + " = " + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " + + "tmp." + Keys.IS_REVOKED + " = '0' AND " + + "tmp." + Keys.CAN_ENCRYPT + " = '1' AND " + + "tmp." + Keys.CREATION + " <= '" + now + "' AND " + + "(tmp." + Keys.EXPIRY + " IS NULL OR " + + "tmp." + Keys.EXPIRY + " >= '" + now + "'))", // 4 + }, + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", + new String[] { "" + Id.database.type_public }, + null, null, orderBy); + + activity.startManagingCursor(mCursor); + } + + public void cleanup() { + if (mCursor != null) { + mActivity.stopManagingCursor(mCursor); + mCursor.close(); + } + } + + @Override + public boolean isEnabled(int position) { + mCursor.moveToPosition(position); + return mCursor.getInt(4) > 0; // valid CAN_ENCRYPT + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public int getCount() { + return mCursor.getCount(); + } + + @Override + public Object getItem(int position) { + return position; + } + + @Override + public long getItemId(int position) { + mCursor.moveToPosition(position); + return mCursor.getLong(1); // MASTER_KEY_ID + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + mCursor.moveToPosition(position); + + View view = mInflater.inflate(R.layout.select_public_key_item, null); + boolean enabled = isEnabled(position); + + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + mainUserId.setText(R.string.unknownUserId); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mainUserIdRest.setText(""); + TextView keyId = (TextView) view.findViewById(R.id.keyId); + keyId.setText(R.string.noKey); + TextView status = (TextView) view.findViewById(R.id.status); + status.setText(R.string.unknownStatus); + + String userId = mCursor.getString(2); // USER_ID + if (userId != null) { + String chunks[] = userId.split(" <", 2); + userId = chunks[0]; + if (chunks.length > 1) { + mainUserIdRest.setText("<" + chunks[1]); + } + mainUserId.setText(userId); + } + + long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID + keyId.setText("" + Long.toHexString(masterKeyId & 0xffffffffL)); + + if (mainUserIdRest.getText().length() == 0) { + mainUserIdRest.setVisibility(View.GONE); + } + + if (enabled) { + status.setText(R.string.canEncrypt); + } else { + if (mCursor.getInt(3) > 0) { + // has some CAN_ENCRYPT keys, but col(4) = 0, so must be revoked or expired + status.setText(R.string.expired); + } else { + status.setText(R.string.noKey); + } + } + + status.setText(status.getText() + " "); + + CheckBox selected = (CheckBox) view.findViewById(R.id.selected); + + if (!enabled) { + mParent.setItemChecked(position, false); + } + + selected.setChecked(mParent.isItemChecked(position)); + + view.setEnabled(enabled); + mainUserId.setEnabled(enabled); + mainUserIdRest.setEnabled(enabled); + keyId.setEnabled(enabled); + selected.setEnabled(enabled); + status.setEnabled(enabled); + + return view; + } +} diff --git a/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java b/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java index d83c2aca6..898a9e6da 100644 --- a/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java +++ b/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java @@ -1,175 +1,175 @@ -package org.thialfihar.android.apg; - -import java.util.Date; - -import org.thialfihar.android.apg.provider.KeyRings; -import org.thialfihar.android.apg.provider.Keys; -import org.thialfihar.android.apg.provider.UserIds; - -import android.app.Activity; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ListView; -import android.widget.TextView; - -public class SelectSecretKeyListAdapter extends BaseAdapter { - protected LayoutInflater mInflater; - protected ListView mParent; - protected SQLiteDatabase mDatabase; - protected Cursor mCursor; - protected String mSearchString; - protected Activity mActivity; - - public SelectSecretKeyListAdapter(Activity activity, ListView parent, String searchString) { - mSearchString = searchString; - - mActivity = activity; - mParent = parent; - mDatabase = Apg.getDatabase().db(); - mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - long now = new Date().getTime() / 1000; - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + - "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + - Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + - ") " + - " INNER JOIN " + UserIds.TABLE_NAME + " ON " + - "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + - UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + - UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); - - if (searchString != null && searchString.trim().length() > 0) { - String[] chunks = searchString.trim().split(" +"); - qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + - UserIds.TABLE_NAME + " AS tmp WHERE " + - "tmp." + UserIds.KEY_ID + " = " + - Keys.TABLE_NAME + "." + Keys._ID); - for (int i = 0; i < chunks.length; ++i) { - qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); - qb.appendWhereEscapeString("%" + chunks[i] + "%"); - } - qb.appendWhere(")"); - } - - mCursor = qb.query(mDatabase, - new String[] { - KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 - KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 - UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 - "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + - "tmp." + Keys.KEY_RING_ID + " = " + - KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " + - "tmp." + Keys.IS_REVOKED + " = '0' AND " + - "tmp." + Keys.CAN_SIGN + " = '1')", // 3, - "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + - "tmp." + Keys.KEY_RING_ID + " = " + - KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " + - "tmp." + Keys.IS_REVOKED + " = '0' AND " + - "tmp." + Keys.CAN_SIGN + " = '1' AND " + - "tmp." + Keys.CREATION + " <= '" + now + "' AND " + - "(tmp." + Keys.EXPIRY + " IS NULL OR " + - "tmp." + Keys.EXPIRY + " >= '" + now + "'))", // 4 - }, - KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", - new String[] { "" + Id.database.type_secret }, - null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"); - - activity.startManagingCursor(mCursor); - } - - public void cleanup() { - if (mCursor != null) { - mActivity.stopManagingCursor(mCursor); - mCursor.close(); - } - } - - @Override - public boolean isEnabled(int position) { - mCursor.moveToPosition(position); - return mCursor.getInt(4) > 0; // valid CAN_SIGN - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public int getCount() { - return mCursor.getCount(); - } - - @Override - public Object getItem(int position) { - return position; - } - - @Override - public long getItemId(int position) { - mCursor.moveToPosition(position); - return mCursor.getLong(1); // MASTER_KEY_ID - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - mCursor.moveToPosition(position); - - View view = mInflater.inflate(R.layout.select_secret_key_item, null); - boolean enabled = isEnabled(position); - - TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(R.string.unknownUserId); - TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); - TextView keyId = (TextView) view.findViewById(R.id.keyId); - keyId.setText(R.string.noKey); - TextView status = (TextView) view.findViewById(R.id.status); - status.setText(R.string.unknownStatus); - - String userId = mCursor.getString(2); // USER_ID - if (userId != null) { - String chunks[] = userId.split(" <", 2); - userId = chunks[0]; - if (chunks.length > 1) { - mainUserIdRest.setText("<" + chunks[1]); - } - mainUserId.setText(userId); - } - - long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID - keyId.setText("" + Long.toHexString(masterKeyId & 0xffffffffL)); - - if (mainUserIdRest.getText().length() == 0) { - mainUserIdRest.setVisibility(View.GONE); - } - - if (enabled) { - status.setText(R.string.canSign); - } else { - if (mCursor.getInt(3) > 0) { - // has some CAN_SIGN keys, but col(4) = 0, so must be revoked or expired - status.setText(R.string.expired); - } else { - status.setText(R.string.noKey); - } - } - - status.setText(status.getText() + " "); - - view.setEnabled(enabled); - mainUserId.setEnabled(enabled); - mainUserIdRest.setEnabled(enabled); - keyId.setEnabled(enabled); - status.setEnabled(enabled); - - return view; - } +package org.thialfihar.android.apg; + +import java.util.Date; + +import org.thialfihar.android.apg.provider.KeyRings; +import org.thialfihar.android.apg.provider.Keys; +import org.thialfihar.android.apg.provider.UserIds; + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + +public class SelectSecretKeyListAdapter extends BaseAdapter { + protected LayoutInflater mInflater; + protected ListView mParent; + protected SQLiteDatabase mDatabase; + protected Cursor mCursor; + protected String mSearchString; + protected Activity mActivity; + + public SelectSecretKeyListAdapter(Activity activity, ListView parent, String searchString) { + mSearchString = searchString; + + mActivity = activity; + mParent = parent; + mDatabase = Apg.getDatabase().db(); + mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + long now = new Date().getTime() / 1000; + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + + "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + + ") " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + + "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); + + if (searchString != null && searchString.trim().length() > 0) { + String[] chunks = searchString.trim().split(" +"); + qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + + UserIds.TABLE_NAME + " AS tmp WHERE " + + "tmp." + UserIds.KEY_ID + " = " + + Keys.TABLE_NAME + "." + Keys._ID); + for (int i = 0; i < chunks.length; ++i) { + qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); + qb.appendWhereEscapeString("%" + chunks[i] + "%"); + } + qb.appendWhere(")"); + } + + mCursor = qb.query(mDatabase, + new String[] { + KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 + UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 + "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + + "tmp." + Keys.KEY_RING_ID + " = " + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " + + "tmp." + Keys.IS_REVOKED + " = '0' AND " + + "tmp." + Keys.CAN_SIGN + " = '1')", // 3, + "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + + "tmp." + Keys.KEY_RING_ID + " = " + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " + + "tmp." + Keys.IS_REVOKED + " = '0' AND " + + "tmp." + Keys.CAN_SIGN + " = '1' AND " + + "tmp." + Keys.CREATION + " <= '" + now + "' AND " + + "(tmp." + Keys.EXPIRY + " IS NULL OR " + + "tmp." + Keys.EXPIRY + " >= '" + now + "'))", // 4 + }, + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", + new String[] { "" + Id.database.type_secret }, + null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"); + + activity.startManagingCursor(mCursor); + } + + public void cleanup() { + if (mCursor != null) { + mActivity.stopManagingCursor(mCursor); + mCursor.close(); + } + } + + @Override + public boolean isEnabled(int position) { + mCursor.moveToPosition(position); + return mCursor.getInt(4) > 0; // valid CAN_SIGN + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public int getCount() { + return mCursor.getCount(); + } + + @Override + public Object getItem(int position) { + return position; + } + + @Override + public long getItemId(int position) { + mCursor.moveToPosition(position); + return mCursor.getLong(1); // MASTER_KEY_ID + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + mCursor.moveToPosition(position); + + View view = mInflater.inflate(R.layout.select_secret_key_item, null); + boolean enabled = isEnabled(position); + + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + mainUserId.setText(R.string.unknownUserId); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mainUserIdRest.setText(""); + TextView keyId = (TextView) view.findViewById(R.id.keyId); + keyId.setText(R.string.noKey); + TextView status = (TextView) view.findViewById(R.id.status); + status.setText(R.string.unknownStatus); + + String userId = mCursor.getString(2); // USER_ID + if (userId != null) { + String chunks[] = userId.split(" <", 2); + userId = chunks[0]; + if (chunks.length > 1) { + mainUserIdRest.setText("<" + chunks[1]); + } + mainUserId.setText(userId); + } + + long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID + keyId.setText("" + Long.toHexString(masterKeyId & 0xffffffffL)); + + if (mainUserIdRest.getText().length() == 0) { + mainUserIdRest.setVisibility(View.GONE); + } + + if (enabled) { + status.setText(R.string.canSign); + } else { + if (mCursor.getInt(3) > 0) { + // has some CAN_SIGN keys, but col(4) = 0, so must be revoked or expired + status.setText(R.string.expired); + } else { + status.setText(R.string.noKey); + } + } + + status.setText(status.getText() + " "); + + view.setEnabled(enabled); + mainUserId.setEnabled(enabled); + mainUserIdRest.setEnabled(enabled); + keyId.setEnabled(enabled); + status.setEnabled(enabled); + + return view; + } } \ No newline at end of file diff --git a/src/org/thialfihar/android/apg/Service.java b/src/org/thialfihar/android/apg/Service.java index 6105473db..70bc80a16 100644 --- a/src/org/thialfihar/android/apg/Service.java +++ b/src/org/thialfihar/android/apg/Service.java @@ -1,78 +1,78 @@ -package org.thialfihar.android.apg; - -import android.content.Intent; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; - -public class Service extends android.app.Service { - private final IBinder mBinder = new LocalBinder(); - - public static final String EXTRA_TTL = "ttl"; - - private int mPassPhraseCacheTtl = 15; - private Handler mCacheHandler = new Handler(); - private Runnable mCacheTask = new Runnable() { - public void run() { - // check every ttl/2 seconds, which shouldn't be heavy on the device (even if ttl = 15), - // and makes sure the longest a pass phrase survives in the cache is 1.5 * ttl - int delay = mPassPhraseCacheTtl * 1000 / 2; - // also make sure the delay is not longer than one minute - if (delay > 60000) { - delay = 60000; - } - - delay = Apg.cleanUpCache(mPassPhraseCacheTtl, delay); - // don't check too often, even if we were close - if (delay < 5000) { - delay = 5000; - } - - mCacheHandler.postDelayed(this, delay); - } - }; - - static private boolean mIsRunning = false; - - @Override - public void onCreate() { - super.onCreate(); - - mIsRunning = true; - } - - @Override - public void onDestroy() { - super.onDestroy(); - mIsRunning = false; - } - - @Override - public void onStart(Intent intent, int startId) { - super.onStart(intent, startId); - - if (intent != null) { - mPassPhraseCacheTtl = intent.getIntExtra(EXTRA_TTL, 15); - } - if (mPassPhraseCacheTtl < 15) { - mPassPhraseCacheTtl = 15; - } - mCacheHandler.removeCallbacks(mCacheTask); - mCacheHandler.postDelayed(mCacheTask, 1000); - } - - static public boolean isRunning() { - return mIsRunning; - } - - public class LocalBinder extends Binder { - Service getService() { - return Service.this; - } - } - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } -} +package org.thialfihar.android.apg; + +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; + +public class Service extends android.app.Service { + private final IBinder mBinder = new LocalBinder(); + + public static final String EXTRA_TTL = "ttl"; + + private int mPassPhraseCacheTtl = 15; + private Handler mCacheHandler = new Handler(); + private Runnable mCacheTask = new Runnable() { + public void run() { + // check every ttl/2 seconds, which shouldn't be heavy on the device (even if ttl = 15), + // and makes sure the longest a pass phrase survives in the cache is 1.5 * ttl + int delay = mPassPhraseCacheTtl * 1000 / 2; + // also make sure the delay is not longer than one minute + if (delay > 60000) { + delay = 60000; + } + + delay = Apg.cleanUpCache(mPassPhraseCacheTtl, delay); + // don't check too often, even if we were close + if (delay < 5000) { + delay = 5000; + } + + mCacheHandler.postDelayed(this, delay); + } + }; + + static private boolean mIsRunning = false; + + @Override + public void onCreate() { + super.onCreate(); + + mIsRunning = true; + } + + @Override + public void onDestroy() { + super.onDestroy(); + mIsRunning = false; + } + + @Override + public void onStart(Intent intent, int startId) { + super.onStart(intent, startId); + + if (intent != null) { + mPassPhraseCacheTtl = intent.getIntExtra(EXTRA_TTL, 15); + } + if (mPassPhraseCacheTtl < 15) { + mPassPhraseCacheTtl = 15; + } + mCacheHandler.removeCallbacks(mCacheTask); + mCacheHandler.postDelayed(mCacheTask, 1000); + } + + static public boolean isRunning() { + return mIsRunning; + } + + public class LocalBinder extends Binder { + Service getService() { + return Service.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } +} diff --git a/src/org/thialfihar/android/apg/provider/Accounts.java b/src/org/thialfihar/android/apg/provider/Accounts.java index 8162472ec..e1829ef4b 100644 --- a/src/org/thialfihar/android/apg/provider/Accounts.java +++ b/src/org/thialfihar/android/apg/provider/Accounts.java @@ -1,27 +1,27 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg.provider; - -import android.provider.BaseColumns; - -public class Accounts implements BaseColumns { - public static final String TABLE_NAME = "accounts"; - - public static final String _ID_type = "INTEGER PRIMARY KEY"; - public static final String NAME = "c_name"; - public static final String NAME_type = "TEXT"; -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg.provider; + +import android.provider.BaseColumns; + +public class Accounts implements BaseColumns { + public static final String TABLE_NAME = "accounts"; + + public static final String _ID_type = "INTEGER PRIMARY KEY"; + public static final String NAME = "c_name"; + public static final String NAME_type = "TEXT"; +} diff --git a/src/org/thialfihar/android/apg/provider/Database.java b/src/org/thialfihar/android/apg/provider/Database.java index 810ebebbf..05beb980d 100644 --- a/src/org/thialfihar/android/apg/provider/Database.java +++ b/src/org/thialfihar/android/apg/provider/Database.java @@ -1,605 +1,605 @@ -package org.thialfihar.android.apg.provider; - -import java.io.IOException; -import java.util.Date; -import java.util.HashMap; -import java.util.Vector; - -import org.bouncycastle2.openpgp.PGPException; -import org.bouncycastle2.openpgp.PGPPublicKey; -import org.bouncycastle2.openpgp.PGPPublicKeyRing; -import org.bouncycastle2.openpgp.PGPSecretKey; -import org.bouncycastle2.openpgp.PGPSecretKeyRing; -import org.thialfihar.android.apg.Apg; -import org.thialfihar.android.apg.Id; -import org.thialfihar.android.apg.utils.IterableIterator; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -public class Database extends SQLiteOpenHelper { - public static class GeneralException extends Exception { - static final long serialVersionUID = 0xf812773343L; - - public GeneralException(String message) { - super(message); - } - } - - private static final String DATABASE_NAME = "apg"; - private static final int DATABASE_VERSION = 2; - - public static final String AUTHORITY = "org.thialfihar.android.apg.database"; - - public static HashMap sKeyRingsProjection; - public static HashMap sKeysProjection; - public static HashMap sUserIdsProjection; - - private SQLiteDatabase mDb = null; - private int mStatus = 0; - - static { - sKeyRingsProjection = new HashMap(); - sKeyRingsProjection.put(KeyRings._ID, KeyRings._ID); - sKeyRingsProjection.put(KeyRings.MASTER_KEY_ID, KeyRings.MASTER_KEY_ID); - sKeyRingsProjection.put(KeyRings.TYPE, KeyRings.TYPE); - sKeyRingsProjection.put(KeyRings.WHO_ID, KeyRings.WHO_ID); - sKeyRingsProjection.put(KeyRings.KEY_RING_DATA, KeyRings.KEY_RING_DATA); - - sKeysProjection = new HashMap(); - sKeysProjection.put(Keys._ID, Keys._ID); - sKeysProjection.put(Keys.KEY_ID, Keys.KEY_ID); - sKeysProjection.put(Keys.TYPE, Keys.TYPE); - sKeysProjection.put(Keys.IS_MASTER_KEY, Keys.IS_MASTER_KEY); - sKeysProjection.put(Keys.ALGORITHM, Keys.ALGORITHM); - sKeysProjection.put(Keys.KEY_SIZE, Keys.KEY_SIZE); - sKeysProjection.put(Keys.CAN_SIGN, Keys.CAN_SIGN); - sKeysProjection.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT); - sKeysProjection.put(Keys.IS_REVOKED, Keys.IS_REVOKED); - sKeysProjection.put(Keys.CREATION, Keys.CREATION); - sKeysProjection.put(Keys.EXPIRY, Keys.EXPIRY); - sKeysProjection.put(Keys.KEY_DATA, Keys.KEY_DATA); - sKeysProjection.put(Keys.RANK, Keys.RANK); - - sUserIdsProjection = new HashMap(); - sUserIdsProjection.put(UserIds._ID, UserIds._ID); - sUserIdsProjection.put(UserIds.KEY_ID, UserIds.KEY_ID); - sUserIdsProjection.put(UserIds.USER_ID, UserIds.USER_ID); - sUserIdsProjection.put(UserIds.RANK, UserIds.RANK); - } - - public Database(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - // force upgrade to test things - //onUpgrade(getWritableDatabase(), 1, 2); - mDb = getWritableDatabase(); - } - - @Override - protected void finalize() throws Throwable { - mDb.close(); - super.finalize(); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" + - KeyRings._ID + " " + KeyRings._ID_type + "," + - KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " + - KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " + - KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " + - KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");"); - - db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" + - Keys._ID + " " + Keys._ID_type + "," + - Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " + - Keys.TYPE + " " + Keys.TYPE_type + ", " + - Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " + - Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " + - Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " + - Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " + - Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " + - Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " + - Keys.CREATION + " " + Keys.CREATION_type + ", " + - Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " + - Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " + - Keys.KEY_DATA + " " + Keys.KEY_DATA_type + - Keys.RANK + " " + Keys.RANK_type + ");"); - - db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" + - UserIds._ID + " " + UserIds._ID_type + "," + - UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," + - UserIds.USER_ID + " " + UserIds.USER_ID_type + "," + - UserIds.RANK + " " + UserIds.RANK_type + ");"); - - db.execSQL("CREATE TABLE " + Accounts.TABLE_NAME + " (" + - Accounts._ID + " " + Accounts._ID_type + "," + - Accounts.NAME + " " + Accounts.NAME_type + ");"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - mDb = db; - for (int version = oldVersion; version < newVersion; ++version) { - switch (version) { - case 1: { // upgrade 1 to 2 - db.execSQL("DROP TABLE IF EXISTS " + KeyRings.TABLE_NAME + ";"); - db.execSQL("DROP TABLE IF EXISTS " + Keys.TABLE_NAME + ";"); - db.execSQL("DROP TABLE IF EXISTS " + UserIds.TABLE_NAME + ";"); - - db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" + - KeyRings._ID + " " + KeyRings._ID_type + "," + - KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " + - KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " + - KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " + - KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");"); - - db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" + - Keys._ID + " " + Keys._ID_type + "," + - Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " + - Keys.TYPE + " " + Keys.TYPE_type + ", " + - Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " + - Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " + - Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " + - Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " + - Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " + - Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " + - Keys.CREATION + " " + Keys.CREATION_type + ", " + - Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " + - Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " + - Keys.KEY_DATA + " " + Keys.KEY_DATA_type + - Keys.RANK + " " + Keys.RANK_type + ");"); - - db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" + - UserIds._ID + " " + UserIds._ID_type + "," + - UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," + - UserIds.USER_ID + " " + UserIds.USER_ID_type + "," + - UserIds.RANK + " " + UserIds.RANK_type + ");"); - - Cursor cursor = db.query("public_keys", new String[] { "c_key_data" }, - null, null, null, null, null); - if (cursor != null && cursor.moveToFirst()) { - do { - byte[] data = cursor.getBlob(0); - try { - PGPPublicKeyRing keyRing = new PGPPublicKeyRing(data); - saveKeyRing(keyRing); - } catch (IOException e) { - Log.e("apg.db.upgrade", "key import failed: " + e); - } catch (GeneralException e) { - Log.e("apg.db.upgrade", "key import failed: " + e); - } - } while (cursor.moveToNext()); - } - - if (cursor != null) { - cursor.close(); - } - - cursor = db.query("secret_keys", new String[]{ "c_key_data" }, - null, null, null, null, null); - if (cursor != null && cursor.moveToFirst()) { - do { - byte[] data = cursor.getBlob(0); - try { - PGPSecretKeyRing keyRing = new PGPSecretKeyRing(data); - saveKeyRing(keyRing); - } catch (IOException e) { - Log.e("apg.db.upgrade", "key import failed: " + e); - } catch (PGPException e) { - Log.e("apg.db.upgrade", "key import failed: " + e); - } catch (GeneralException e) { - Log.e("apg.db.upgrade", "key import failed: " + e); - } - } while (cursor.moveToNext()); - } - - if (cursor != null) { - cursor.close(); - } - - db.execSQL("DROP TABLE IF EXISTS public_keys;"); - db.execSQL("DROP TABLE IF EXISTS secret_keys;"); - - break; - } - - default: { - break; - } - } - } - mDb = null; - } - - public int saveKeyRing(PGPPublicKeyRing keyRing) throws IOException, GeneralException { - mDb.beginTransaction(); - ContentValues values = new ContentValues(); - PGPPublicKey masterKey = keyRing.getPublicKey(); - long masterKeyId = masterKey.getKeyID(); - - values.put(KeyRings.MASTER_KEY_ID, masterKeyId); - values.put(KeyRings.TYPE, Id.database.type_public); - values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); - - long rowId = insertOrUpdateKeyRing(values); - int returnValue = mStatus; - - if (rowId == -1) { - throw new GeneralException("saving public key ring " + masterKeyId + " failed"); - } - - Vector seenIds = new Vector(); - int rank = 0; - for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { - seenIds.add(saveKey(rowId, key, rank)); - ++rank; - } - - String seenIdsStr = ""; - for (Integer id : seenIds) { - if (seenIdsStr.length() > 0) { - seenIdsStr += ","; - } - seenIdsStr += id; - } - mDb.delete(Keys.TABLE_NAME, - Keys.KEY_RING_ID + " = ? AND " + - Keys._ID + " NOT IN (" + seenIdsStr + ")", - new String[] { "" + rowId }); - - mDb.setTransactionSuccessful(); - mDb.endTransaction(); - return returnValue; - } - - public int saveKeyRing(PGPSecretKeyRing keyRing) throws IOException, GeneralException { - mDb.beginTransaction(); - ContentValues values = new ContentValues(); - PGPSecretKey masterKey = keyRing.getSecretKey(); - long masterKeyId = masterKey.getKeyID(); - - values.put(KeyRings.MASTER_KEY_ID, masterKeyId); - values.put(KeyRings.TYPE, Id.database.type_secret); - values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); - - long rowId = insertOrUpdateKeyRing(values); - int returnValue = mStatus; - - if (rowId == -1) { - throw new GeneralException("saving secret key ring " + masterKeyId + " failed"); - } - - Vector seenIds = new Vector(); - int rank = 0; - for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { - seenIds.add(saveKey(rowId, key, rank)); - ++rank; - } - - String seenIdsStr = ""; - for (Integer id : seenIds) { - if (seenIdsStr.length() > 0) { - seenIdsStr += ","; - } - seenIdsStr += id; - } - mDb.delete(Keys.TABLE_NAME, - Keys.KEY_RING_ID + " = ? AND " + - Keys._ID + " NOT IN (" + seenIdsStr + ")", - new String[] { "" + rowId }); - - mDb.setTransactionSuccessful(); - mDb.endTransaction(); - return returnValue; - } - - private int saveKey(long keyRingId, PGPPublicKey key, int rank) - throws IOException, GeneralException { - ContentValues values = new ContentValues(); - - values.put(Keys.KEY_ID, key.getKeyID()); - values.put(Keys.TYPE, Id.database.type_public); - values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); - values.put(Keys.ALGORITHM, key.getAlgorithm()); - values.put(Keys.KEY_SIZE, key.getBitStrength()); - values.put(Keys.CAN_SIGN, Apg.isSigningKey(key)); - values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key)); - values.put(Keys.IS_REVOKED, key.isRevoked()); - values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000); - Date expiryDate = Apg.getExpiryDate(key); - if (expiryDate != null) { - values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); - } - values.put(Keys.KEY_RING_ID, keyRingId); - values.put(Keys.KEY_DATA, key.getEncoded()); - values.put(Keys.RANK, rank); - - long rowId = insertOrUpdateKey(values); - - if (rowId == -1) { - throw new GeneralException("saving public key " + key.getKeyID() + " failed"); - } - - Vector seenIds = new Vector(); - int userIdRank = 0; - for (String userId : new IterableIterator(key.getUserIDs())) { - seenIds.add(saveUserId(rowId, userId, userIdRank)); - ++userIdRank; - } - - String seenIdsStr = ""; - for (Integer id : seenIds) { - if (seenIdsStr.length() > 0) { - seenIdsStr += ","; - } - seenIdsStr += id; - } - mDb.delete(UserIds.TABLE_NAME, - UserIds.KEY_ID + " = ? AND " + - UserIds._ID + " NOT IN (" + seenIdsStr + ")", - new String[] { "" + rowId }); - - return (int)rowId; - } - - private int saveKey(long keyRingId, PGPSecretKey key, int rank) - throws IOException, GeneralException { - ContentValues values = new ContentValues(); - - values.put(Keys.KEY_ID, key.getPublicKey().getKeyID()); - values.put(Keys.TYPE, Id.database.type_secret); - values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); - values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm()); - values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength()); - values.put(Keys.CAN_SIGN, Apg.isSigningKey(key)); - values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key)); - values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked()); - values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000); - Date expiryDate = Apg.getExpiryDate(key); - if (expiryDate != null) { - values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); - } - values.put(Keys.KEY_RING_ID, keyRingId); - values.put(Keys.KEY_DATA, key.getEncoded()); - values.put(Keys.RANK, rank); - - long rowId = insertOrUpdateKey(values); - - if (rowId == -1) { - throw new GeneralException("saving secret key " + key.getPublicKey().getKeyID() + " failed"); - } - - Vector seenIds = new Vector(); - int userIdRank = 0; - for (String userId : new IterableIterator(key.getUserIDs())) { - seenIds.add(saveUserId(rowId, userId, userIdRank)); - ++userIdRank; - } - - String seenIdsStr = ""; - for (Integer id : seenIds) { - if (seenIdsStr.length() > 0) { - seenIdsStr += ","; - } - seenIdsStr += id; - } - mDb.delete(UserIds.TABLE_NAME, - UserIds.KEY_ID + " = ? AND " + - UserIds._ID + " NOT IN (" + seenIdsStr + ")", - new String[] { "" + rowId }); - - return (int)rowId; - } - - private int saveUserId(long keyId, String userId, int rank) throws GeneralException { - ContentValues values = new ContentValues(); - - values.put(UserIds.KEY_ID, keyId); - values.put(UserIds.USER_ID, userId); - values.put(UserIds.RANK, rank); - - long rowId = insertOrUpdateUserId(values); - - if (rowId == -1) { - throw new GeneralException("saving user id " + userId + " failed"); - } - - return (int)rowId; - } - - private long insertOrUpdateKeyRing(ContentValues values) { - Cursor c = mDb.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID }, - KeyRings.MASTER_KEY_ID + " = ? AND " + KeyRings.TYPE + " = ?", - new String[] { - values.getAsString(KeyRings.MASTER_KEY_ID), - values.getAsString(KeyRings.TYPE), - }, - null, null, null); - long rowId = -1; - if (c != null && c.moveToFirst()) { - rowId = c.getLong(0); - mDb.update(KeyRings.TABLE_NAME, values, - KeyRings._ID + " = ?", new String[] { "" + rowId }); - mStatus = Id.return_value.updated; - } else { - rowId = mDb.insert(KeyRings.TABLE_NAME, KeyRings.WHO_ID, values); - mStatus = Id.return_value.ok; - } - - if (c != null) { - c.close(); - } - - return rowId; - } - - private long insertOrUpdateKey(ContentValues values) { - Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, - Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", - new String[] { - values.getAsString(Keys.KEY_ID), - values.getAsString(Keys.TYPE), - }, - null, null, null); - long rowId = -1; - if (c != null && c.moveToFirst()) { - rowId = c.getLong(0); - mDb.update(Keys.TABLE_NAME, values, - Keys._ID + " = ?", new String[] { "" + rowId }); - } else { - rowId = mDb.insert(Keys.TABLE_NAME, Keys.KEY_DATA, values); - } - - if (c != null) { - c.close(); - } - - return rowId; - } - - private long insertOrUpdateUserId(ContentValues values) { - Cursor c = mDb.query(UserIds.TABLE_NAME, new String[] { UserIds._ID }, - UserIds.KEY_ID + " = ? AND " + UserIds.USER_ID + " = ?", - new String[] { - values.getAsString(UserIds.KEY_ID), - values.getAsString(UserIds.USER_ID), - }, - null, null, null); - long rowId = -1; - if (c != null && c.moveToFirst()) { - rowId = c.getLong(0); - mDb.update(UserIds.TABLE_NAME, values, - UserIds._ID + " = ?", new String[] { "" + rowId }); - } else { - rowId = mDb.insert(UserIds.TABLE_NAME, UserIds.USER_ID, values); - } - - if (c != null) { - c.close(); - } - - return rowId; - } - - public Object getKeyRing(int keyRingId) { - Cursor c = mDb.query(KeyRings.TABLE_NAME, - new String[] { KeyRings.KEY_RING_DATA, KeyRings.TYPE }, - KeyRings._ID + " = ?", - new String[] { - "" + keyRingId, - }, - null, null, null); - byte[] data = null; - Object keyRing = null; - if (c != null && c.moveToFirst()) { - data = c.getBlob(0); - if (data != null) { - try { - if (c.getInt(1) == Id.database.type_public) { - keyRing = new PGPPublicKeyRing(data); - } else { - keyRing = new PGPSecretKeyRing(data); - } - } catch (IOException e) { - // can't load it, then - } catch (PGPException e) { - // can't load it, then - } - } - } - - if (c != null) { - c.close(); - } - - return keyRing; - } - - public byte[] getKeyRingDataFromKeyId(int type, long keyId) { - Cursor c = mDb.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + - KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ")", - new String[] { KeyRings.TABLE_NAME + "." + KeyRings.KEY_RING_DATA }, - Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " + - KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", - new String[] { - "" + keyId, - "" + type, - }, - null, null, null); - - byte[] data = null; - if (c != null && c.moveToFirst()) { - data = c.getBlob(0); - } - - if (c != null) { - c.close(); - } - - return data; - } - - public byte[] getKeyDataFromKeyId(int type, long keyId) { - Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys.KEY_DATA }, - Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", - new String[] { - "" + keyId, - "" + type, - }, - null, null, null); - byte[] data = null; - if (c != null && c.moveToFirst()) { - data = c.getBlob(0); - } - - if (c != null) { - c.close(); - } - - return data; - } - - public void deleteKeyRing(int keyRingId) { - mDb.beginTransaction(); - mDb.delete(KeyRings.TABLE_NAME, - KeyRings._ID + " = ?", new String[] { "" + keyRingId }); - - Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, - Keys.KEY_RING_ID + " = ?", - new String[] { - "" + keyRingId, - }, - null, null, null); - if (c != null && c.moveToFirst()) { - do { - int keyId = c.getInt(0); - deleteKey(keyId); - } while (c.moveToNext()); - } - - if (c != null) { - c.close(); - } - - mDb.setTransactionSuccessful(); - mDb.endTransaction(); - } - - private void deleteKey(int keyId) { - mDb.delete(Keys.TABLE_NAME, - Keys._ID + " = ?", new String[] { "" + keyId }); - - mDb.delete(UserIds.TABLE_NAME, - UserIds.KEY_ID + " = ?", new String[] { "" + keyId }); - } - - public SQLiteDatabase db() { - return mDb; - } -} +package org.thialfihar.android.apg.provider; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Vector; + +import org.bouncycastle2.openpgp.PGPException; +import org.bouncycastle2.openpgp.PGPPublicKey; +import org.bouncycastle2.openpgp.PGPPublicKeyRing; +import org.bouncycastle2.openpgp.PGPSecretKey; +import org.bouncycastle2.openpgp.PGPSecretKeyRing; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.utils.IterableIterator; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +public class Database extends SQLiteOpenHelper { + public static class GeneralException extends Exception { + static final long serialVersionUID = 0xf812773343L; + + public GeneralException(String message) { + super(message); + } + } + + private static final String DATABASE_NAME = "apg"; + private static final int DATABASE_VERSION = 2; + + public static final String AUTHORITY = "org.thialfihar.android.apg.database"; + + public static HashMap sKeyRingsProjection; + public static HashMap sKeysProjection; + public static HashMap sUserIdsProjection; + + private SQLiteDatabase mDb = null; + private int mStatus = 0; + + static { + sKeyRingsProjection = new HashMap(); + sKeyRingsProjection.put(KeyRings._ID, KeyRings._ID); + sKeyRingsProjection.put(KeyRings.MASTER_KEY_ID, KeyRings.MASTER_KEY_ID); + sKeyRingsProjection.put(KeyRings.TYPE, KeyRings.TYPE); + sKeyRingsProjection.put(KeyRings.WHO_ID, KeyRings.WHO_ID); + sKeyRingsProjection.put(KeyRings.KEY_RING_DATA, KeyRings.KEY_RING_DATA); + + sKeysProjection = new HashMap(); + sKeysProjection.put(Keys._ID, Keys._ID); + sKeysProjection.put(Keys.KEY_ID, Keys.KEY_ID); + sKeysProjection.put(Keys.TYPE, Keys.TYPE); + sKeysProjection.put(Keys.IS_MASTER_KEY, Keys.IS_MASTER_KEY); + sKeysProjection.put(Keys.ALGORITHM, Keys.ALGORITHM); + sKeysProjection.put(Keys.KEY_SIZE, Keys.KEY_SIZE); + sKeysProjection.put(Keys.CAN_SIGN, Keys.CAN_SIGN); + sKeysProjection.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT); + sKeysProjection.put(Keys.IS_REVOKED, Keys.IS_REVOKED); + sKeysProjection.put(Keys.CREATION, Keys.CREATION); + sKeysProjection.put(Keys.EXPIRY, Keys.EXPIRY); + sKeysProjection.put(Keys.KEY_DATA, Keys.KEY_DATA); + sKeysProjection.put(Keys.RANK, Keys.RANK); + + sUserIdsProjection = new HashMap(); + sUserIdsProjection.put(UserIds._ID, UserIds._ID); + sUserIdsProjection.put(UserIds.KEY_ID, UserIds.KEY_ID); + sUserIdsProjection.put(UserIds.USER_ID, UserIds.USER_ID); + sUserIdsProjection.put(UserIds.RANK, UserIds.RANK); + } + + public Database(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + // force upgrade to test things + //onUpgrade(getWritableDatabase(), 1, 2); + mDb = getWritableDatabase(); + } + + @Override + protected void finalize() throws Throwable { + mDb.close(); + super.finalize(); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" + + KeyRings._ID + " " + KeyRings._ID_type + "," + + KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " + + KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " + + KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " + + KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");"); + + db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" + + Keys._ID + " " + Keys._ID_type + "," + + Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " + + Keys.TYPE + " " + Keys.TYPE_type + ", " + + Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " + + Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " + + Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " + + Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " + + Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " + + Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " + + Keys.CREATION + " " + Keys.CREATION_type + ", " + + Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " + + Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " + + Keys.KEY_DATA + " " + Keys.KEY_DATA_type + + Keys.RANK + " " + Keys.RANK_type + ");"); + + db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" + + UserIds._ID + " " + UserIds._ID_type + "," + + UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," + + UserIds.USER_ID + " " + UserIds.USER_ID_type + "," + + UserIds.RANK + " " + UserIds.RANK_type + ");"); + + db.execSQL("CREATE TABLE " + Accounts.TABLE_NAME + " (" + + Accounts._ID + " " + Accounts._ID_type + "," + + Accounts.NAME + " " + Accounts.NAME_type + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + mDb = db; + for (int version = oldVersion; version < newVersion; ++version) { + switch (version) { + case 1: { // upgrade 1 to 2 + db.execSQL("DROP TABLE IF EXISTS " + KeyRings.TABLE_NAME + ";"); + db.execSQL("DROP TABLE IF EXISTS " + Keys.TABLE_NAME + ";"); + db.execSQL("DROP TABLE IF EXISTS " + UserIds.TABLE_NAME + ";"); + + db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" + + KeyRings._ID + " " + KeyRings._ID_type + "," + + KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " + + KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " + + KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " + + KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");"); + + db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" + + Keys._ID + " " + Keys._ID_type + "," + + Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " + + Keys.TYPE + " " + Keys.TYPE_type + ", " + + Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " + + Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " + + Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " + + Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " + + Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " + + Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " + + Keys.CREATION + " " + Keys.CREATION_type + ", " + + Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " + + Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " + + Keys.KEY_DATA + " " + Keys.KEY_DATA_type + + Keys.RANK + " " + Keys.RANK_type + ");"); + + db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" + + UserIds._ID + " " + UserIds._ID_type + "," + + UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," + + UserIds.USER_ID + " " + UserIds.USER_ID_type + "," + + UserIds.RANK + " " + UserIds.RANK_type + ");"); + + Cursor cursor = db.query("public_keys", new String[] { "c_key_data" }, + null, null, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + do { + byte[] data = cursor.getBlob(0); + try { + PGPPublicKeyRing keyRing = new PGPPublicKeyRing(data); + saveKeyRing(keyRing); + } catch (IOException e) { + Log.e("apg.db.upgrade", "key import failed: " + e); + } catch (GeneralException e) { + Log.e("apg.db.upgrade", "key import failed: " + e); + } + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + + cursor = db.query("secret_keys", new String[]{ "c_key_data" }, + null, null, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + do { + byte[] data = cursor.getBlob(0); + try { + PGPSecretKeyRing keyRing = new PGPSecretKeyRing(data); + saveKeyRing(keyRing); + } catch (IOException e) { + Log.e("apg.db.upgrade", "key import failed: " + e); + } catch (PGPException e) { + Log.e("apg.db.upgrade", "key import failed: " + e); + } catch (GeneralException e) { + Log.e("apg.db.upgrade", "key import failed: " + e); + } + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + + db.execSQL("DROP TABLE IF EXISTS public_keys;"); + db.execSQL("DROP TABLE IF EXISTS secret_keys;"); + + break; + } + + default: { + break; + } + } + } + mDb = null; + } + + public int saveKeyRing(PGPPublicKeyRing keyRing) throws IOException, GeneralException { + mDb.beginTransaction(); + ContentValues values = new ContentValues(); + PGPPublicKey masterKey = keyRing.getPublicKey(); + long masterKeyId = masterKey.getKeyID(); + + values.put(KeyRings.MASTER_KEY_ID, masterKeyId); + values.put(KeyRings.TYPE, Id.database.type_public); + values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); + + long rowId = insertOrUpdateKeyRing(values); + int returnValue = mStatus; + + if (rowId == -1) { + throw new GeneralException("saving public key ring " + masterKeyId + " failed"); + } + + Vector seenIds = new Vector(); + int rank = 0; + for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { + seenIds.add(saveKey(rowId, key, rank)); + ++rank; + } + + String seenIdsStr = ""; + for (Integer id : seenIds) { + if (seenIdsStr.length() > 0) { + seenIdsStr += ","; + } + seenIdsStr += id; + } + mDb.delete(Keys.TABLE_NAME, + Keys.KEY_RING_ID + " = ? AND " + + Keys._ID + " NOT IN (" + seenIdsStr + ")", + new String[] { "" + rowId }); + + mDb.setTransactionSuccessful(); + mDb.endTransaction(); + return returnValue; + } + + public int saveKeyRing(PGPSecretKeyRing keyRing) throws IOException, GeneralException { + mDb.beginTransaction(); + ContentValues values = new ContentValues(); + PGPSecretKey masterKey = keyRing.getSecretKey(); + long masterKeyId = masterKey.getKeyID(); + + values.put(KeyRings.MASTER_KEY_ID, masterKeyId); + values.put(KeyRings.TYPE, Id.database.type_secret); + values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); + + long rowId = insertOrUpdateKeyRing(values); + int returnValue = mStatus; + + if (rowId == -1) { + throw new GeneralException("saving secret key ring " + masterKeyId + " failed"); + } + + Vector seenIds = new Vector(); + int rank = 0; + for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { + seenIds.add(saveKey(rowId, key, rank)); + ++rank; + } + + String seenIdsStr = ""; + for (Integer id : seenIds) { + if (seenIdsStr.length() > 0) { + seenIdsStr += ","; + } + seenIdsStr += id; + } + mDb.delete(Keys.TABLE_NAME, + Keys.KEY_RING_ID + " = ? AND " + + Keys._ID + " NOT IN (" + seenIdsStr + ")", + new String[] { "" + rowId }); + + mDb.setTransactionSuccessful(); + mDb.endTransaction(); + return returnValue; + } + + private int saveKey(long keyRingId, PGPPublicKey key, int rank) + throws IOException, GeneralException { + ContentValues values = new ContentValues(); + + values.put(Keys.KEY_ID, key.getKeyID()); + values.put(Keys.TYPE, Id.database.type_public); + values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); + values.put(Keys.ALGORITHM, key.getAlgorithm()); + values.put(Keys.KEY_SIZE, key.getBitStrength()); + values.put(Keys.CAN_SIGN, Apg.isSigningKey(key)); + values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key)); + values.put(Keys.IS_REVOKED, key.isRevoked()); + values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000); + Date expiryDate = Apg.getExpiryDate(key); + if (expiryDate != null) { + values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); + } + values.put(Keys.KEY_RING_ID, keyRingId); + values.put(Keys.KEY_DATA, key.getEncoded()); + values.put(Keys.RANK, rank); + + long rowId = insertOrUpdateKey(values); + + if (rowId == -1) { + throw new GeneralException("saving public key " + key.getKeyID() + " failed"); + } + + Vector seenIds = new Vector(); + int userIdRank = 0; + for (String userId : new IterableIterator(key.getUserIDs())) { + seenIds.add(saveUserId(rowId, userId, userIdRank)); + ++userIdRank; + } + + String seenIdsStr = ""; + for (Integer id : seenIds) { + if (seenIdsStr.length() > 0) { + seenIdsStr += ","; + } + seenIdsStr += id; + } + mDb.delete(UserIds.TABLE_NAME, + UserIds.KEY_ID + " = ? AND " + + UserIds._ID + " NOT IN (" + seenIdsStr + ")", + new String[] { "" + rowId }); + + return (int)rowId; + } + + private int saveKey(long keyRingId, PGPSecretKey key, int rank) + throws IOException, GeneralException { + ContentValues values = new ContentValues(); + + values.put(Keys.KEY_ID, key.getPublicKey().getKeyID()); + values.put(Keys.TYPE, Id.database.type_secret); + values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); + values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm()); + values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength()); + values.put(Keys.CAN_SIGN, Apg.isSigningKey(key)); + values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key)); + values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked()); + values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000); + Date expiryDate = Apg.getExpiryDate(key); + if (expiryDate != null) { + values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); + } + values.put(Keys.KEY_RING_ID, keyRingId); + values.put(Keys.KEY_DATA, key.getEncoded()); + values.put(Keys.RANK, rank); + + long rowId = insertOrUpdateKey(values); + + if (rowId == -1) { + throw new GeneralException("saving secret key " + key.getPublicKey().getKeyID() + " failed"); + } + + Vector seenIds = new Vector(); + int userIdRank = 0; + for (String userId : new IterableIterator(key.getUserIDs())) { + seenIds.add(saveUserId(rowId, userId, userIdRank)); + ++userIdRank; + } + + String seenIdsStr = ""; + for (Integer id : seenIds) { + if (seenIdsStr.length() > 0) { + seenIdsStr += ","; + } + seenIdsStr += id; + } + mDb.delete(UserIds.TABLE_NAME, + UserIds.KEY_ID + " = ? AND " + + UserIds._ID + " NOT IN (" + seenIdsStr + ")", + new String[] { "" + rowId }); + + return (int)rowId; + } + + private int saveUserId(long keyId, String userId, int rank) throws GeneralException { + ContentValues values = new ContentValues(); + + values.put(UserIds.KEY_ID, keyId); + values.put(UserIds.USER_ID, userId); + values.put(UserIds.RANK, rank); + + long rowId = insertOrUpdateUserId(values); + + if (rowId == -1) { + throw new GeneralException("saving user id " + userId + " failed"); + } + + return (int)rowId; + } + + private long insertOrUpdateKeyRing(ContentValues values) { + Cursor c = mDb.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID }, + KeyRings.MASTER_KEY_ID + " = ? AND " + KeyRings.TYPE + " = ?", + new String[] { + values.getAsString(KeyRings.MASTER_KEY_ID), + values.getAsString(KeyRings.TYPE), + }, + null, null, null); + long rowId = -1; + if (c != null && c.moveToFirst()) { + rowId = c.getLong(0); + mDb.update(KeyRings.TABLE_NAME, values, + KeyRings._ID + " = ?", new String[] { "" + rowId }); + mStatus = Id.return_value.updated; + } else { + rowId = mDb.insert(KeyRings.TABLE_NAME, KeyRings.WHO_ID, values); + mStatus = Id.return_value.ok; + } + + if (c != null) { + c.close(); + } + + return rowId; + } + + private long insertOrUpdateKey(ContentValues values) { + Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, + Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", + new String[] { + values.getAsString(Keys.KEY_ID), + values.getAsString(Keys.TYPE), + }, + null, null, null); + long rowId = -1; + if (c != null && c.moveToFirst()) { + rowId = c.getLong(0); + mDb.update(Keys.TABLE_NAME, values, + Keys._ID + " = ?", new String[] { "" + rowId }); + } else { + rowId = mDb.insert(Keys.TABLE_NAME, Keys.KEY_DATA, values); + } + + if (c != null) { + c.close(); + } + + return rowId; + } + + private long insertOrUpdateUserId(ContentValues values) { + Cursor c = mDb.query(UserIds.TABLE_NAME, new String[] { UserIds._ID }, + UserIds.KEY_ID + " = ? AND " + UserIds.USER_ID + " = ?", + new String[] { + values.getAsString(UserIds.KEY_ID), + values.getAsString(UserIds.USER_ID), + }, + null, null, null); + long rowId = -1; + if (c != null && c.moveToFirst()) { + rowId = c.getLong(0); + mDb.update(UserIds.TABLE_NAME, values, + UserIds._ID + " = ?", new String[] { "" + rowId }); + } else { + rowId = mDb.insert(UserIds.TABLE_NAME, UserIds.USER_ID, values); + } + + if (c != null) { + c.close(); + } + + return rowId; + } + + public Object getKeyRing(int keyRingId) { + Cursor c = mDb.query(KeyRings.TABLE_NAME, + new String[] { KeyRings.KEY_RING_DATA, KeyRings.TYPE }, + KeyRings._ID + " = ?", + new String[] { + "" + keyRingId, + }, + null, null, null); + byte[] data = null; + Object keyRing = null; + if (c != null && c.moveToFirst()) { + data = c.getBlob(0); + if (data != null) { + try { + if (c.getInt(1) == Id.database.type_public) { + keyRing = new PGPPublicKeyRing(data); + } else { + keyRing = new PGPSecretKeyRing(data); + } + } catch (IOException e) { + // can't load it, then + } catch (PGPException e) { + // can't load it, then + } + } + } + + if (c != null) { + c.close(); + } + + return keyRing; + } + + public byte[] getKeyRingDataFromKeyId(int type, long keyId) { + Cursor c = mDb.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ")", + new String[] { KeyRings.TABLE_NAME + "." + KeyRings.KEY_RING_DATA }, + Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " + + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", + new String[] { + "" + keyId, + "" + type, + }, + null, null, null); + + byte[] data = null; + if (c != null && c.moveToFirst()) { + data = c.getBlob(0); + } + + if (c != null) { + c.close(); + } + + return data; + } + + public byte[] getKeyDataFromKeyId(int type, long keyId) { + Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys.KEY_DATA }, + Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", + new String[] { + "" + keyId, + "" + type, + }, + null, null, null); + byte[] data = null; + if (c != null && c.moveToFirst()) { + data = c.getBlob(0); + } + + if (c != null) { + c.close(); + } + + return data; + } + + public void deleteKeyRing(int keyRingId) { + mDb.beginTransaction(); + mDb.delete(KeyRings.TABLE_NAME, + KeyRings._ID + " = ?", new String[] { "" + keyRingId }); + + Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, + Keys.KEY_RING_ID + " = ?", + new String[] { + "" + keyRingId, + }, + null, null, null); + if (c != null && c.moveToFirst()) { + do { + int keyId = c.getInt(0); + deleteKey(keyId); + } while (c.moveToNext()); + } + + if (c != null) { + c.close(); + } + + mDb.setTransactionSuccessful(); + mDb.endTransaction(); + } + + private void deleteKey(int keyId) { + mDb.delete(Keys.TABLE_NAME, + Keys._ID + " = ?", new String[] { "" + keyId }); + + mDb.delete(UserIds.TABLE_NAME, + UserIds.KEY_ID + " = ?", new String[] { "" + keyId }); + } + + public SQLiteDatabase db() { + return mDb; + } +} diff --git a/src/org/thialfihar/android/apg/provider/KeyRings.java b/src/org/thialfihar/android/apg/provider/KeyRings.java index a4eae6695..58e95eba6 100644 --- a/src/org/thialfihar/android/apg/provider/KeyRings.java +++ b/src/org/thialfihar/android/apg/provider/KeyRings.java @@ -1,33 +1,33 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg.provider; - -import android.provider.BaseColumns; - -public class KeyRings implements BaseColumns { - public static final String TABLE_NAME = "key_rings"; - - public static final String _ID_type = "INTEGER PRIMARY KEY"; - public static final String MASTER_KEY_ID = "c_master_key_id"; - public static final String MASTER_KEY_ID_type = "INT64"; - public static final String TYPE = "c_type"; - public static final String TYPE_type = "INTEGER"; - public static final String WHO_ID = "c_who_id"; - public static final String WHO_ID_type = "INTEGER"; - public static final String KEY_RING_DATA = "c_key_ring_data"; - public static final String KEY_RING_DATA_type = "BLOB"; -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg.provider; + +import android.provider.BaseColumns; + +public class KeyRings implements BaseColumns { + public static final String TABLE_NAME = "key_rings"; + + public static final String _ID_type = "INTEGER PRIMARY KEY"; + public static final String MASTER_KEY_ID = "c_master_key_id"; + public static final String MASTER_KEY_ID_type = "INT64"; + public static final String TYPE = "c_type"; + public static final String TYPE_type = "INTEGER"; + public static final String WHO_ID = "c_who_id"; + public static final String WHO_ID_type = "INTEGER"; + public static final String KEY_RING_DATA = "c_key_ring_data"; + public static final String KEY_RING_DATA_type = "BLOB"; +} diff --git a/src/org/thialfihar/android/apg/provider/UserIds.java b/src/org/thialfihar/android/apg/provider/UserIds.java index 2b1162beb..2050ccf9c 100644 --- a/src/org/thialfihar/android/apg/provider/UserIds.java +++ b/src/org/thialfihar/android/apg/provider/UserIds.java @@ -1,31 +1,31 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg.provider; - -import android.provider.BaseColumns; - -public class UserIds implements BaseColumns { - public static final String TABLE_NAME = "user_ids"; - - public static final String _ID_type = "INTEGER PRIMARY KEY"; - public static final String KEY_ID = "c_key_id"; - public static final String KEY_ID_type = "INTEGER"; - public static final String USER_ID = "c_user_id"; - public static final String USER_ID_type = "TEXT"; - public static final String RANK = "c_rank"; - public static final String RANK_type = "INTEGER"; -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg.provider; + +import android.provider.BaseColumns; + +public class UserIds implements BaseColumns { + public static final String TABLE_NAME = "user_ids"; + + public static final String _ID_type = "INTEGER PRIMARY KEY"; + public static final String KEY_ID = "c_key_id"; + public static final String KEY_ID_type = "INTEGER"; + public static final String USER_ID = "c_user_id"; + public static final String USER_ID_type = "TEXT"; + public static final String RANK = "c_rank"; + public static final String RANK_type = "INTEGER"; +} diff --git a/src/org/thialfihar/android/apg/ui/widget/Editor.java b/src/org/thialfihar/android/apg/ui/widget/Editor.java index ab29581a4..a7200fad0 100644 --- a/src/org/thialfihar/android/apg/ui/widget/Editor.java +++ b/src/org/thialfihar/android/apg/ui/widget/Editor.java @@ -1,25 +1,25 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg.ui.widget; - -public interface Editor { - public interface EditorListener { - public void onDeleted(Editor editor); - } - - public void setEditorListener(EditorListener listener); -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg.ui.widget; + +public interface Editor { + public interface EditorListener { + public void onDeleted(Editor editor); + } + + public void setEditorListener(EditorListener listener); +} diff --git a/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java b/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java index 1a9cfdbac..1bc7515f5 100644 --- a/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java +++ b/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java @@ -1,242 +1,242 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg.ui.widget; - -import java.text.DateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Vector; - -import org.bouncycastle2.openpgp.PGPPublicKey; -import org.bouncycastle2.openpgp.PGPSecretKey; -import org.thialfihar.android.apg.Apg; -import org.thialfihar.android.apg.Id; -import org.thialfihar.android.apg.R; -import org.thialfihar.android.apg.utils.Choice; - -import android.app.DatePickerDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.DatePicker; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.Spinner; -import android.widget.TextView; - -public class KeyEditor extends LinearLayout implements Editor, OnClickListener { - private PGPSecretKey mKey; - - private EditorListener mEditorListener = null; - - private boolean mIsMasterKey; - ImageButton mDeleteButton; - TextView mAlgorithm; - TextView mKeyId; - Spinner mUsage; - TextView mCreationDate; - Button mExpiryDateButton; - GregorianCalendar mExpiryDate; - - private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = - new DatePickerDialog.OnDateSetListener() { - public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { - GregorianCalendar date = new GregorianCalendar(year, monthOfYear, dayOfMonth); - setExpiryDate(date); - } - }; - - public KeyEditor(Context context) { - super(context); - } - - public KeyEditor(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mAlgorithm = (TextView) findViewById(R.id.algorithm); - mKeyId = (TextView) findViewById(R.id.keyId); - mCreationDate = (TextView) findViewById(R.id.creation); - mExpiryDateButton = (Button) findViewById(R.id.expiry); - mUsage = (Spinner) findViewById(R.id.usage); - Choice choices[] = { - new Choice(Id.choice.usage.sign_only, - getResources().getString(R.string.choice_signOnly)), - new Choice(Id.choice.usage.encrypt_only, - getResources().getString(R.string.choice_encryptOnly)), - new Choice(Id.choice.usage.sign_and_encrypt, - getResources().getString(R.string.choice_signAndEncrypt)), - }; - ArrayAdapter adapter = - new ArrayAdapter(getContext(), - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mUsage.setAdapter(adapter); - - mDeleteButton = (ImageButton) findViewById(R.id.delete); - mDeleteButton.setOnClickListener(this); - - setExpiryDate(null); - - mExpiryDateButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - GregorianCalendar date = mExpiryDate; - if (date == null) { - date = new GregorianCalendar(); - } - - DatePickerDialog dialog = - new DatePickerDialog(getContext(), mExpiryDateSetListener, - date.get(Calendar.YEAR), - date.get(Calendar.MONTH), - date.get(Calendar.DAY_OF_MONTH)); - dialog.setCancelable(true); - dialog.setButton(Dialog.BUTTON_NEGATIVE, - getContext().getString(R.string.btn_noDate), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - setExpiryDate(null); - } - }); - dialog.show(); - } - }); - - super.onFinishInflate(); - } - - public void setValue(PGPSecretKey key, boolean isMasterKey) { - mKey = key; - - mIsMasterKey = isMasterKey; - if (mIsMasterKey) { - mDeleteButton.setVisibility(View.INVISIBLE); - } - - mAlgorithm.setText(Apg.getAlgorithmInfo(key)); - String keyId1Str = Long.toHexString((key.getKeyID() >> 32) & 0xffffffffL); - while (keyId1Str.length() < 8) { - keyId1Str = "0" + keyId1Str; - } - String keyId2Str = Long.toHexString(key.getKeyID() & 0xffffffffL); - while (keyId2Str.length() < 8) { - keyId2Str = "0" + keyId2Str; - } - mKeyId.setText(keyId1Str + " " + keyId2Str); - - Vector choices = new Vector(); - boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT); - if (!isElGamalKey) { - choices.add(new Choice(Id.choice.usage.sign_only, - getResources().getString(R.string.choice_signOnly))); - } - if (!mIsMasterKey) { - choices.add(new Choice(Id.choice.usage.encrypt_only, - getResources().getString(R.string.choice_encryptOnly))); - } - if (!isElGamalKey) { - choices.add(new Choice(Id.choice.usage.sign_and_encrypt, - getResources().getString(R.string.choice_signAndEncrypt))); - } - - ArrayAdapter adapter = - new ArrayAdapter(getContext(), - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mUsage.setAdapter(adapter); - - int selectId = 0; - if (Apg.isEncryptionKey(key)) { - if (Apg.isSigningKey(key)) { - selectId = Id.choice.usage.sign_and_encrypt; - } else { - selectId = Id.choice.usage.encrypt_only; - } - } else { - selectId = Id.choice.usage.sign_only; - } - - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == selectId) { - mUsage.setSelection(i); - break; - } - } - - GregorianCalendar cal = new GregorianCalendar(); - cal.setTime(Apg.getCreationDate(key)); - mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime())); - cal = new GregorianCalendar(); - Date date = Apg.getExpiryDate(key); - if (date == null) { - setExpiryDate(null); - } else { - cal.setTime(Apg.getExpiryDate(key)); - setExpiryDate(cal); - } - } - - public PGPSecretKey getValue() { - return mKey; - } - - @Override - public void onClick(View v) { - final ViewGroup parent = (ViewGroup)getParent(); - if (v == mDeleteButton) { - parent.removeView(this); - if (mEditorListener != null) { - mEditorListener.onDeleted(this); - } - } - } - - @Override - public void setEditorListener(EditorListener listener) { - mEditorListener = listener; - } - - private void setExpiryDate(GregorianCalendar date) { - mExpiryDate = date; - if (date == null) { - mExpiryDateButton.setText(R.string.none); - } else { - mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime())); - } - } - - public GregorianCalendar getExpiryDate() { - return mExpiryDate; - } - - public int getUsage() { - return ((Choice) mUsage.getSelectedItem()).getId(); - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg.ui.widget; + +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Vector; + +import org.bouncycastle2.openpgp.PGPPublicKey; +import org.bouncycastle2.openpgp.PGPSecretKey; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.utils.Choice; + +import android.app.DatePickerDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.DatePicker; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; + +public class KeyEditor extends LinearLayout implements Editor, OnClickListener { + private PGPSecretKey mKey; + + private EditorListener mEditorListener = null; + + private boolean mIsMasterKey; + ImageButton mDeleteButton; + TextView mAlgorithm; + TextView mKeyId; + Spinner mUsage; + TextView mCreationDate; + Button mExpiryDateButton; + GregorianCalendar mExpiryDate; + + private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = + new DatePickerDialog.OnDateSetListener() { + public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { + GregorianCalendar date = new GregorianCalendar(year, monthOfYear, dayOfMonth); + setExpiryDate(date); + } + }; + + public KeyEditor(Context context) { + super(context); + } + + public KeyEditor(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + setDrawingCacheEnabled(true); + setAlwaysDrawnWithCacheEnabled(true); + + mAlgorithm = (TextView) findViewById(R.id.algorithm); + mKeyId = (TextView) findViewById(R.id.keyId); + mCreationDate = (TextView) findViewById(R.id.creation); + mExpiryDateButton = (Button) findViewById(R.id.expiry); + mUsage = (Spinner) findViewById(R.id.usage); + Choice choices[] = { + new Choice(Id.choice.usage.sign_only, + getResources().getString(R.string.choice_signOnly)), + new Choice(Id.choice.usage.encrypt_only, + getResources().getString(R.string.choice_encryptOnly)), + new Choice(Id.choice.usage.sign_and_encrypt, + getResources().getString(R.string.choice_signAndEncrypt)), + }; + ArrayAdapter adapter = + new ArrayAdapter(getContext(), + android.R.layout.simple_spinner_item, choices); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mUsage.setAdapter(adapter); + + mDeleteButton = (ImageButton) findViewById(R.id.delete); + mDeleteButton.setOnClickListener(this); + + setExpiryDate(null); + + mExpiryDateButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + GregorianCalendar date = mExpiryDate; + if (date == null) { + date = new GregorianCalendar(); + } + + DatePickerDialog dialog = + new DatePickerDialog(getContext(), mExpiryDateSetListener, + date.get(Calendar.YEAR), + date.get(Calendar.MONTH), + date.get(Calendar.DAY_OF_MONTH)); + dialog.setCancelable(true); + dialog.setButton(Dialog.BUTTON_NEGATIVE, + getContext().getString(R.string.btn_noDate), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + setExpiryDate(null); + } + }); + dialog.show(); + } + }); + + super.onFinishInflate(); + } + + public void setValue(PGPSecretKey key, boolean isMasterKey) { + mKey = key; + + mIsMasterKey = isMasterKey; + if (mIsMasterKey) { + mDeleteButton.setVisibility(View.INVISIBLE); + } + + mAlgorithm.setText(Apg.getAlgorithmInfo(key)); + String keyId1Str = Long.toHexString((key.getKeyID() >> 32) & 0xffffffffL); + while (keyId1Str.length() < 8) { + keyId1Str = "0" + keyId1Str; + } + String keyId2Str = Long.toHexString(key.getKeyID() & 0xffffffffL); + while (keyId2Str.length() < 8) { + keyId2Str = "0" + keyId2Str; + } + mKeyId.setText(keyId1Str + " " + keyId2Str); + + Vector choices = new Vector(); + boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT); + if (!isElGamalKey) { + choices.add(new Choice(Id.choice.usage.sign_only, + getResources().getString(R.string.choice_signOnly))); + } + if (!mIsMasterKey) { + choices.add(new Choice(Id.choice.usage.encrypt_only, + getResources().getString(R.string.choice_encryptOnly))); + } + if (!isElGamalKey) { + choices.add(new Choice(Id.choice.usage.sign_and_encrypt, + getResources().getString(R.string.choice_signAndEncrypt))); + } + + ArrayAdapter adapter = + new ArrayAdapter(getContext(), + android.R.layout.simple_spinner_item, choices); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mUsage.setAdapter(adapter); + + int selectId = 0; + if (Apg.isEncryptionKey(key)) { + if (Apg.isSigningKey(key)) { + selectId = Id.choice.usage.sign_and_encrypt; + } else { + selectId = Id.choice.usage.encrypt_only; + } + } else { + selectId = Id.choice.usage.sign_only; + } + + for (int i = 0; i < choices.size(); ++i) { + if (choices.get(i).getId() == selectId) { + mUsage.setSelection(i); + break; + } + } + + GregorianCalendar cal = new GregorianCalendar(); + cal.setTime(Apg.getCreationDate(key)); + mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime())); + cal = new GregorianCalendar(); + Date date = Apg.getExpiryDate(key); + if (date == null) { + setExpiryDate(null); + } else { + cal.setTime(Apg.getExpiryDate(key)); + setExpiryDate(cal); + } + } + + public PGPSecretKey getValue() { + return mKey; + } + + @Override + public void onClick(View v) { + final ViewGroup parent = (ViewGroup)getParent(); + if (v == mDeleteButton) { + parent.removeView(this); + if (mEditorListener != null) { + mEditorListener.onDeleted(this); + } + } + } + + @Override + public void setEditorListener(EditorListener listener) { + mEditorListener = listener; + } + + private void setExpiryDate(GregorianCalendar date) { + mExpiryDate = date; + if (date == null) { + mExpiryDateButton.setText(R.string.none); + } else { + mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime())); + } + } + + public GregorianCalendar getExpiryDate() { + return mExpiryDate; + } + + public int getUsage() { + return ((Choice) mUsage.getSelectedItem()).getId(); + } +} diff --git a/src/org/thialfihar/android/apg/ui/widget/SectionView.java b/src/org/thialfihar/android/apg/ui/widget/SectionView.java index 3b7d801de..124c1c9da 100644 --- a/src/org/thialfihar/android/apg/ui/widget/SectionView.java +++ b/src/org/thialfihar/android/apg/ui/widget/SectionView.java @@ -1,337 +1,337 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg.ui.widget; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidParameterException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.util.Vector; - -import org.bouncycastle2.openpgp.PGPException; -import org.bouncycastle2.openpgp.PGPSecretKey; -import org.thialfihar.android.apg.Apg; -import org.thialfihar.android.apg.Id; -import org.thialfihar.android.apg.R; -import org.thialfihar.android.apg.ui.widget.Editor.EditorListener; -import org.thialfihar.android.apg.utils.Choice; - -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.Toast; - -public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Runnable { - private LayoutInflater mInflater; - private View mAdd; - private ViewGroup mEditors; - private TextView mTitle; - private int mType = 0; - - private Choice mNewKeyAlgorithmChoice; - private int mNewKeySize; - - volatile private PGPSecretKey mNewKey; - private ProgressDialog mProgressDialog; - private Thread mRunningThread = null; - - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - Bundle data = msg.getData(); - if (data != null) { - boolean closeProgressDialog = data.getBoolean("closeProgressDialog"); - if (closeProgressDialog) { - if (mProgressDialog != null) { - mProgressDialog.dismiss(); - mProgressDialog = null; - } - } - - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(getContext(), - getContext().getString(R.string.errorMessage, error), - Toast.LENGTH_SHORT).show(); - } - - boolean gotNewKey = data.getBoolean("gotNewKey"); - if (gotNewKey) { - KeyEditor view = - (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, - mEditors, false); - view.setEditorListener(SectionView.this); - boolean isMasterKey = (mEditors.getChildCount() == 0); - view.setValue(mNewKey, isMasterKey); - mEditors.addView(view); - SectionView.this.updateEditorsVisible(); - } - } - } - }; - - public SectionView(Context context) { - super(context); - } - - public SectionView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ViewGroup getEditors() { - return mEditors; - } - - public void setType(int type) { - mType = type; - switch (type) { - case Id.type.user_id: { - mTitle.setText(R.string.section_userIds); - break; - } - - case Id.type.key: { - mTitle.setText(R.string.section_keys); - break; - } - - default: { - break; - } - } - } - - /** {@inheritDoc} */ - @Override - protected void onFinishInflate() { - mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mAdd = findViewById(R.id.header); - mAdd.setOnClickListener(this); - - mEditors = (ViewGroup) findViewById(R.id.editors); - mTitle = (TextView) findViewById(R.id.title); - - updateEditorsVisible(); - super.onFinishInflate(); - } - - /** {@inheritDoc} */ - public void onDeleted(Editor editor) { - this.updateEditorsVisible(); - } - - protected void updateEditorsVisible() { - final boolean hasChildren = mEditors.getChildCount() > 0; - mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE); - } - - /** {@inheritDoc} */ - public void onClick(View v) { - switch (mType) { - case Id.type.user_id: { - UserIdEditor view = - (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, - mEditors, false); - view.setEditorListener(this); - if (mEditors.getChildCount() == 0) { - view.setIsMainUserId(true); - } - mEditors.addView(view); - break; - } - - case Id.type.key: { - AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); - - View view = mInflater.inflate(R.layout.create_key, null); - dialog.setView(view); - dialog.setTitle(R.string.title_createKey); - dialog.setMessage(R.string.keyCreationElGamalInfo); - - boolean wouldBeMasterKey = (mEditors.getChildCount() == 0); - - final Spinner algorithm = (Spinner) view.findViewById(R.id.algorithm); - Vector choices = new Vector(); - choices.add(new Choice(Id.choice.algorithm.dsa, - getResources().getString(R.string.dsa))); - if (!wouldBeMasterKey) { - choices.add(new Choice(Id.choice.algorithm.elgamal, - getResources().getString(R.string.elgamal))); - } - - choices.add(new Choice(Id.choice.algorithm.rsa, - getResources().getString(R.string.rsa))); - - ArrayAdapter adapter = - new ArrayAdapter(getContext(), - android.R.layout.simple_spinner_item, - choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - algorithm.setAdapter(adapter); - // make RSA the default - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == Id.choice.algorithm.rsa) { - algorithm.setSelection(i); - break; - } - } - - final EditText keySize = (EditText) view.findViewById(R.id.size); - - dialog.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface di, int id) { - di.dismiss(); - try { - mNewKeySize = Integer.parseInt("" + keySize.getText()); - } catch (NumberFormatException e) { - mNewKeySize = 0; - } - - mNewKeyAlgorithmChoice = (Choice) algorithm.getSelectedItem(); - createKey(); - } - }); - - dialog.setCancelable(true); - dialog.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface di, int id) { - di.dismiss(); - } - }); - - dialog.create().show(); - break; - } - - default: { - break; - } - } - this.updateEditorsVisible(); - } - - public void setUserIds(Vector list) { - if (mType != Id.type.user_id) { - return; - } - - mEditors.removeAllViews(); - for (String userId : list) { - UserIdEditor view = - (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, mEditors, false); - view.setEditorListener(this); - view.setValue(userId); - if (mEditors.getChildCount() == 0) { - view.setIsMainUserId(true); - } - mEditors.addView(view); - } - - this.updateEditorsVisible(); - } - - public void setKeys(Vector list) { - if (mType != Id.type.key) { - return; - } - - mEditors.removeAllViews(); - for (PGPSecretKey key : list) { - KeyEditor view = - (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors, false); - view.setEditorListener(this); - boolean isMasterKey = (mEditors.getChildCount() == 0); - view.setValue(key, isMasterKey); - mEditors.addView(view); - } - - this.updateEditorsVisible(); - } - - private void createKey() { - mProgressDialog = new ProgressDialog(getContext()); - mProgressDialog.setMessage(getContext().getString(R.string.progress_generating)); - mProgressDialog.setCancelable(false); - mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - mProgressDialog.show(); - mRunningThread = new Thread(this); - mRunningThread.start(); - } - - public void run() { - String error = null; - try { - PGPSecretKey masterKey = null; - String passPhrase; - if (mEditors.getChildCount() > 0) { - masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue(); - passPhrase = Apg.getCachedPassPhrase(masterKey.getKeyID()); - } else { - passPhrase = ""; - } - mNewKey = Apg.createKey(getContext(), - mNewKeyAlgorithmChoice.getId(), - mNewKeySize, passPhrase, - masterKey); - } catch (NoSuchProviderException e) { - error = "" + e; - } catch (NoSuchAlgorithmException e) { - error = "" + e; - } catch (PGPException e) { - error = "" + e; - } catch (InvalidParameterException e) { - error = "" + e; - } catch (InvalidAlgorithmParameterException e) { - error = "" + e; - } catch (Apg.GeneralException e) { - error = "" + e; - } - - Message message = new Message(); - Bundle data = new Bundle(); - data.putBoolean("closeProgressDialog", true); - if (error != null) { - data.putString(Apg.EXTRA_ERROR, error); - } else { - data.putBoolean("gotNewKey", true); - } - message.setData(data); - mHandler.sendMessage(message); - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg.ui.widget; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Vector; + +import org.bouncycastle2.openpgp.PGPException; +import org.bouncycastle2.openpgp.PGPSecretKey; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.ui.widget.Editor.EditorListener; +import org.thialfihar.android.apg.utils.Choice; + +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Runnable { + private LayoutInflater mInflater; + private View mAdd; + private ViewGroup mEditors; + private TextView mTitle; + private int mType = 0; + + private Choice mNewKeyAlgorithmChoice; + private int mNewKeySize; + + volatile private PGPSecretKey mNewKey; + private ProgressDialog mProgressDialog; + private Thread mRunningThread = null; + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + Bundle data = msg.getData(); + if (data != null) { + boolean closeProgressDialog = data.getBoolean("closeProgressDialog"); + if (closeProgressDialog) { + if (mProgressDialog != null) { + mProgressDialog.dismiss(); + mProgressDialog = null; + } + } + + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(getContext(), + getContext().getString(R.string.errorMessage, error), + Toast.LENGTH_SHORT).show(); + } + + boolean gotNewKey = data.getBoolean("gotNewKey"); + if (gotNewKey) { + KeyEditor view = + (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, + mEditors, false); + view.setEditorListener(SectionView.this); + boolean isMasterKey = (mEditors.getChildCount() == 0); + view.setValue(mNewKey, isMasterKey); + mEditors.addView(view); + SectionView.this.updateEditorsVisible(); + } + } + } + }; + + public SectionView(Context context) { + super(context); + } + + public SectionView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ViewGroup getEditors() { + return mEditors; + } + + public void setType(int type) { + mType = type; + switch (type) { + case Id.type.user_id: { + mTitle.setText(R.string.section_userIds); + break; + } + + case Id.type.key: { + mTitle.setText(R.string.section_keys); + break; + } + + default: { + break; + } + } + } + + /** {@inheritDoc} */ + @Override + protected void onFinishInflate() { + mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + setDrawingCacheEnabled(true); + setAlwaysDrawnWithCacheEnabled(true); + + mAdd = findViewById(R.id.header); + mAdd.setOnClickListener(this); + + mEditors = (ViewGroup) findViewById(R.id.editors); + mTitle = (TextView) findViewById(R.id.title); + + updateEditorsVisible(); + super.onFinishInflate(); + } + + /** {@inheritDoc} */ + public void onDeleted(Editor editor) { + this.updateEditorsVisible(); + } + + protected void updateEditorsVisible() { + final boolean hasChildren = mEditors.getChildCount() > 0; + mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE); + } + + /** {@inheritDoc} */ + public void onClick(View v) { + switch (mType) { + case Id.type.user_id: { + UserIdEditor view = + (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, + mEditors, false); + view.setEditorListener(this); + if (mEditors.getChildCount() == 0) { + view.setIsMainUserId(true); + } + mEditors.addView(view); + break; + } + + case Id.type.key: { + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); + + View view = mInflater.inflate(R.layout.create_key, null); + dialog.setView(view); + dialog.setTitle(R.string.title_createKey); + dialog.setMessage(R.string.keyCreationElGamalInfo); + + boolean wouldBeMasterKey = (mEditors.getChildCount() == 0); + + final Spinner algorithm = (Spinner) view.findViewById(R.id.algorithm); + Vector choices = new Vector(); + choices.add(new Choice(Id.choice.algorithm.dsa, + getResources().getString(R.string.dsa))); + if (!wouldBeMasterKey) { + choices.add(new Choice(Id.choice.algorithm.elgamal, + getResources().getString(R.string.elgamal))); + } + + choices.add(new Choice(Id.choice.algorithm.rsa, + getResources().getString(R.string.rsa))); + + ArrayAdapter adapter = + new ArrayAdapter(getContext(), + android.R.layout.simple_spinner_item, + choices); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + algorithm.setAdapter(adapter); + // make RSA the default + for (int i = 0; i < choices.size(); ++i) { + if (choices.get(i).getId() == Id.choice.algorithm.rsa) { + algorithm.setSelection(i); + break; + } + } + + final EditText keySize = (EditText) view.findViewById(R.id.size); + + dialog.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface di, int id) { + di.dismiss(); + try { + mNewKeySize = Integer.parseInt("" + keySize.getText()); + } catch (NumberFormatException e) { + mNewKeySize = 0; + } + + mNewKeyAlgorithmChoice = (Choice) algorithm.getSelectedItem(); + createKey(); + } + }); + + dialog.setCancelable(true); + dialog.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface di, int id) { + di.dismiss(); + } + }); + + dialog.create().show(); + break; + } + + default: { + break; + } + } + this.updateEditorsVisible(); + } + + public void setUserIds(Vector list) { + if (mType != Id.type.user_id) { + return; + } + + mEditors.removeAllViews(); + for (String userId : list) { + UserIdEditor view = + (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, mEditors, false); + view.setEditorListener(this); + view.setValue(userId); + if (mEditors.getChildCount() == 0) { + view.setIsMainUserId(true); + } + mEditors.addView(view); + } + + this.updateEditorsVisible(); + } + + public void setKeys(Vector list) { + if (mType != Id.type.key) { + return; + } + + mEditors.removeAllViews(); + for (PGPSecretKey key : list) { + KeyEditor view = + (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors, false); + view.setEditorListener(this); + boolean isMasterKey = (mEditors.getChildCount() == 0); + view.setValue(key, isMasterKey); + mEditors.addView(view); + } + + this.updateEditorsVisible(); + } + + private void createKey() { + mProgressDialog = new ProgressDialog(getContext()); + mProgressDialog.setMessage(getContext().getString(R.string.progress_generating)); + mProgressDialog.setCancelable(false); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + mProgressDialog.show(); + mRunningThread = new Thread(this); + mRunningThread.start(); + } + + public void run() { + String error = null; + try { + PGPSecretKey masterKey = null; + String passPhrase; + if (mEditors.getChildCount() > 0) { + masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue(); + passPhrase = Apg.getCachedPassPhrase(masterKey.getKeyID()); + } else { + passPhrase = ""; + } + mNewKey = Apg.createKey(getContext(), + mNewKeyAlgorithmChoice.getId(), + mNewKeySize, passPhrase, + masterKey); + } catch (NoSuchProviderException e) { + error = "" + e; + } catch (NoSuchAlgorithmException e) { + error = "" + e; + } catch (PGPException e) { + error = "" + e; + } catch (InvalidParameterException e) { + error = "" + e; + } catch (InvalidAlgorithmParameterException e) { + error = "" + e; + } catch (Apg.GeneralException e) { + error = "" + e; + } + + Message message = new Message(); + Bundle data = new Bundle(); + data.putBoolean("closeProgressDialog", true); + if (error != null) { + data.putString(Apg.EXTRA_ERROR, error); + } else { + data.putBoolean("gotNewKey", true); + } + message.setData(data); + mHandler.sendMessage(message); + } +} diff --git a/src/org/thialfihar/android/apg/ui/widget/UserIdEditor.java b/src/org/thialfihar/android/apg/ui/widget/UserIdEditor.java index 555a54375..c8f9f74e7 100644 --- a/src/org/thialfihar/android/apg/ui/widget/UserIdEditor.java +++ b/src/org/thialfihar/android/apg/ui/widget/UserIdEditor.java @@ -1,194 +1,194 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg.ui.widget; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.thialfihar.android.apg.R; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.RadioButton; - -public class UserIdEditor extends LinearLayout implements Editor, OnClickListener { - private EditorListener mEditorListener = null; - - private ImageButton mDeleteButton; - private RadioButton mIsMainUserId; - private EditText mName; - private EditText mEmail; - private EditText mComment; - - private static final Pattern EMAIL_PATTERN = - Pattern.compile("^([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+[.]([a-zA-Z])+([a-zA-Z])+", - Pattern.CASE_INSENSITIVE); - - public static class NoNameException extends Exception { - static final long serialVersionUID = 0xf812773343L; - - public NoNameException(String message) { - super(message); - } - } - - public static class NoEmailException extends Exception { - static final long serialVersionUID = 0xf812773344L; - - public NoEmailException(String message) { - super(message); - } - } - - public static class InvalidEmailException extends Exception { - static final long serialVersionUID = 0xf812773345L; - - public InvalidEmailException(String message) { - super(message); - } - } - - public UserIdEditor(Context context) { - super(context); - } - - public UserIdEditor(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mDeleteButton = (ImageButton) findViewById(R.id.delete); - mDeleteButton.setOnClickListener(this); - mIsMainUserId = (RadioButton) findViewById(R.id.isMainUserId); - mIsMainUserId.setOnClickListener(this); - - mName = (EditText) findViewById(R.id.name); - mEmail = (EditText) findViewById(R.id.email); - mComment = (EditText) findViewById(R.id.comment); - - super.onFinishInflate(); - } - - public void setValue(String userId) { - mName.setText(""); - mComment.setText(""); - mEmail.setText(""); - - Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$"); - Matcher matcher = withComment.matcher(userId); - if (matcher.matches()) { - mName.setText(matcher.group(1)); - mComment.setText(matcher.group(2)); - mEmail.setText(matcher.group(3)); - return; - } - - Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$"); - matcher = withoutComment.matcher(userId); - if (matcher.matches()) { - mName.setText(matcher.group(1)); - mEmail.setText(matcher.group(2)); - return; - } - } - - public String getValue() throws NoNameException, NoEmailException, InvalidEmailException { - String name = ("" + mName.getText()).trim(); - String email = ("" + mEmail.getText()).trim(); - String comment = ("" + mComment.getText()).trim(); - - if (email.length() > 0) { - Matcher emailMatcher = EMAIL_PATTERN.matcher(email); - if (!emailMatcher.matches()) { - throw new InvalidEmailException( - getContext().getString(R.string.error_invalidEmail, email)); - } - } - - String userId = name; - if (comment.length() > 0) { - userId += " (" + comment + ")"; - } - if (email.length() > 0) { - userId += " <" + email + ">"; - } - - if (userId.equals("")) { - // ok, empty one... - return userId; - } - - // otherwise make sure that name and email exist - if (name.equals("")) { - throw new NoNameException("need a name"); - } - - if (email.equals("")) { - throw new NoEmailException("need an email"); - } - - return userId; - } - - @Override - public void onClick(View v) { - final ViewGroup parent = (ViewGroup)getParent(); - if (v == mDeleteButton) { - boolean wasMainUserId = mIsMainUserId.isChecked(); - parent.removeView(this); - if (mEditorListener != null) { - mEditorListener.onDeleted(this); - } - if (wasMainUserId && parent.getChildCount() > 0) { - UserIdEditor editor = (UserIdEditor) parent.getChildAt(0); - editor.setIsMainUserId(true); - } - } else if (v == mIsMainUserId) { - for (int i = 0; i < parent.getChildCount(); ++i) { - UserIdEditor editor = (UserIdEditor) parent.getChildAt(i); - if (editor == this) { - editor.setIsMainUserId(true); - } else { - editor.setIsMainUserId(false); - } - } - } - } - - public void setIsMainUserId(boolean value) { - mIsMainUserId.setChecked(value); - } - - public boolean isMainUserId() { - return mIsMainUserId.isChecked(); - } - - @Override - public void setEditorListener(EditorListener listener) { - mEditorListener = listener; - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg.ui.widget; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.thialfihar.android.apg.R; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.RadioButton; + +public class UserIdEditor extends LinearLayout implements Editor, OnClickListener { + private EditorListener mEditorListener = null; + + private ImageButton mDeleteButton; + private RadioButton mIsMainUserId; + private EditText mName; + private EditText mEmail; + private EditText mComment; + + private static final Pattern EMAIL_PATTERN = + Pattern.compile("^([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+[.]([a-zA-Z])+([a-zA-Z])+", + Pattern.CASE_INSENSITIVE); + + public static class NoNameException extends Exception { + static final long serialVersionUID = 0xf812773343L; + + public NoNameException(String message) { + super(message); + } + } + + public static class NoEmailException extends Exception { + static final long serialVersionUID = 0xf812773344L; + + public NoEmailException(String message) { + super(message); + } + } + + public static class InvalidEmailException extends Exception { + static final long serialVersionUID = 0xf812773345L; + + public InvalidEmailException(String message) { + super(message); + } + } + + public UserIdEditor(Context context) { + super(context); + } + + public UserIdEditor(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + setDrawingCacheEnabled(true); + setAlwaysDrawnWithCacheEnabled(true); + + mDeleteButton = (ImageButton) findViewById(R.id.delete); + mDeleteButton.setOnClickListener(this); + mIsMainUserId = (RadioButton) findViewById(R.id.isMainUserId); + mIsMainUserId.setOnClickListener(this); + + mName = (EditText) findViewById(R.id.name); + mEmail = (EditText) findViewById(R.id.email); + mComment = (EditText) findViewById(R.id.comment); + + super.onFinishInflate(); + } + + public void setValue(String userId) { + mName.setText(""); + mComment.setText(""); + mEmail.setText(""); + + Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$"); + Matcher matcher = withComment.matcher(userId); + if (matcher.matches()) { + mName.setText(matcher.group(1)); + mComment.setText(matcher.group(2)); + mEmail.setText(matcher.group(3)); + return; + } + + Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$"); + matcher = withoutComment.matcher(userId); + if (matcher.matches()) { + mName.setText(matcher.group(1)); + mEmail.setText(matcher.group(2)); + return; + } + } + + public String getValue() throws NoNameException, NoEmailException, InvalidEmailException { + String name = ("" + mName.getText()).trim(); + String email = ("" + mEmail.getText()).trim(); + String comment = ("" + mComment.getText()).trim(); + + if (email.length() > 0) { + Matcher emailMatcher = EMAIL_PATTERN.matcher(email); + if (!emailMatcher.matches()) { + throw new InvalidEmailException( + getContext().getString(R.string.error_invalidEmail, email)); + } + } + + String userId = name; + if (comment.length() > 0) { + userId += " (" + comment + ")"; + } + if (email.length() > 0) { + userId += " <" + email + ">"; + } + + if (userId.equals("")) { + // ok, empty one... + return userId; + } + + // otherwise make sure that name and email exist + if (name.equals("")) { + throw new NoNameException("need a name"); + } + + if (email.equals("")) { + throw new NoEmailException("need an email"); + } + + return userId; + } + + @Override + public void onClick(View v) { + final ViewGroup parent = (ViewGroup)getParent(); + if (v == mDeleteButton) { + boolean wasMainUserId = mIsMainUserId.isChecked(); + parent.removeView(this); + if (mEditorListener != null) { + mEditorListener.onDeleted(this); + } + if (wasMainUserId && parent.getChildCount() > 0) { + UserIdEditor editor = (UserIdEditor) parent.getChildAt(0); + editor.setIsMainUserId(true); + } + } else if (v == mIsMainUserId) { + for (int i = 0; i < parent.getChildCount(); ++i) { + UserIdEditor editor = (UserIdEditor) parent.getChildAt(i); + if (editor == this) { + editor.setIsMainUserId(true); + } else { + editor.setIsMainUserId(false); + } + } + } + } + + public void setIsMainUserId(boolean value) { + mIsMainUserId.setChecked(value); + } + + public boolean isMainUserId() { + return mIsMainUserId.isChecked(); + } + + @Override + public void setEditorListener(EditorListener listener) { + mEditorListener = listener; + } +} diff --git a/src/org/thialfihar/android/apg/utils/Choice.java b/src/org/thialfihar/android/apg/utils/Choice.java index d094c1e1e..4014a1f4a 100644 --- a/src/org/thialfihar/android/apg/utils/Choice.java +++ b/src/org/thialfihar/android/apg/utils/Choice.java @@ -1,44 +1,44 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg.utils; - -public class Choice { - private String mName; - private int mId; - - public Choice() { - mId = -1; - mName = ""; - } - - public Choice(int id, String name) { - mId = id; - mName = name; - } - - public int getId() { - return mId; - } - - public String getName() { - return mName; - } - - public String toString() { - return mName; - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg.utils; + +public class Choice { + private String mName; + private int mId; + + public Choice() { + mId = -1; + mName = ""; + } + + public Choice(int id, String name) { + mId = id; + mName = name; + } + + public int getId() { + return mId; + } + + public String getName() { + return mName; + } + + public String toString() { + return mName; + } +} diff --git a/src/org/thialfihar/android/apg/utils/IterableIterator.java b/src/org/thialfihar/android/apg/utils/IterableIterator.java index ff02c4194..1d9664e38 100644 --- a/src/org/thialfihar/android/apg/utils/IterableIterator.java +++ b/src/org/thialfihar/android/apg/utils/IterableIterator.java @@ -1,31 +1,31 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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.thialfihar.android.apg.utils; - -import java.util.Iterator; - -public class IterableIterator implements Iterable { - private Iterator mIter; - - public IterableIterator(Iterator iter) { - mIter = iter; - } - - public Iterator iterator() { - return mIter; - } -} +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg.utils; + +import java.util.Iterator; + +public class IterableIterator implements Iterable { + private Iterator mIter; + + public IterableIterator(Iterator iter) { + mIter = iter; + } + + public Iterator iterator() { + return mIter; + } +} -- cgit v1.2.3