From 4b8400685a62afa0f4cb21940e9583d21886535b Mon Sep 17 00:00:00 2001 From: Dominik Date: Tue, 11 Sep 2012 19:56:54 +0200 Subject: completly new PasswordCacheService (more energy efficient), reworked Password dialogs --- .../org/thialfihar/android/apg/ApgApplication.java | 3 +- .../android/apg/deprecated/AskForPassphrase.java | 156 ---------------- .../android/apg/deprecated/BaseActivity.java | 12 +- .../org/thialfihar/android/apg/helper/PGPMain.java | 67 +------ .../thialfihar/android/apg/service/ApgService.java | 66 ++++--- .../android/apg/service/CachedPassphrase.java | 22 ++- .../apg/service/PassphraseCacheService.java | 205 ++++++++++++++++----- .../thialfihar/android/apg/ui/DecryptActivity.java | 5 +- .../thialfihar/android/apg/ui/EncryptActivity.java | 6 +- .../android/apg/ui/PreferencesActivity.java | 3 +- .../android/apg/ui/SecretKeyListActivity.java | 8 +- .../thialfihar/android/apg/ui/SignKeyActivity.java | 5 +- .../apg/ui/dialog/PassphraseDialogFragment.java | 62 ++++++- .../apg/ui/dialog/SetPassphraseDialogFragment.java | 48 ++++- .../android/apg/ui/widget/SectionView.java | 4 +- .../org/thialfihar/android/apg/util/KeyServer.java | 1 - 16 files changed, 343 insertions(+), 330 deletions(-) delete mode 100644 org_apg/src/org/thialfihar/android/apg/deprecated/AskForPassphrase.java (limited to 'org_apg/src/org/thialfihar/android') diff --git a/org_apg/src/org/thialfihar/android/apg/ApgApplication.java b/org_apg/src/org/thialfihar/android/apg/ApgApplication.java index de3c1e745..359aa08cf 100644 --- a/org_apg/src/org/thialfihar/android/apg/ApgApplication.java +++ b/org_apg/src/org/thialfihar/android/apg/ApgApplication.java @@ -38,7 +38,8 @@ public class ApgApplication extends Application { super.onCreate(); /* Start passphrase cache service */ - PassphraseCacheService.startCacheService(this); + // TODO: not needed anymore! + // PassphraseCacheService.startCacheService(this); // TODO: Do it better than this! // this initializes the database to be used in PGPMain diff --git a/org_apg/src/org/thialfihar/android/apg/deprecated/AskForPassphrase.java b/org_apg/src/org/thialfihar/android/apg/deprecated/AskForPassphrase.java deleted file mode 100644 index 90cd39176..000000000 --- a/org_apg/src/org/thialfihar/android/apg/deprecated/AskForPassphrase.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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.deprecated; - -import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPPrivateKey; -import org.spongycastle.openpgp.PGPSecretKey; -import org.thialfihar.android.apg.Id; -import org.thialfihar.android.apg.R; -import org.thialfihar.android.apg.helper.PGPHelper; -import org.thialfihar.android.apg.helper.PGPMain; - -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 org.thialfihar.android.apg.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -/** - * TODO: - * - * - Use new PassphraseDialogFragment! - * - * - */ -public class AskForPassphrase { - 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_authentication); - - 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 = PGPHelper.getMasterKey(PGPMain.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() { - public void onClick(DialogInterface dialog, int which) { - activity.removeDialog(Id.dialog.pass_phrase); - } - }); - alert.setCancelable(false); - return alert.create(); - } - String userId = PGPHelper.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.passphrase, null); - final EditText input = (EditText) view.findViewById(R.id.passphrase_passphrase); - - final TextView labelNotUsed = (TextView) view - .findViewById(R.id.passphrase_label_passphrase_again); - labelNotUsed.setVisibility(View.GONE); - final EditText inputNotUsed = (EditText) view - .findViewById(R.id.passphrase_passphrase_again); - 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().toString(); - long keyId; - if (secretKey != null) { - try { - PGPPrivateKey testKey = secretKey.extractPrivateKey( - passPhrase.toCharArray(), new BouncyCastleProvider()); - if (testKey == null) { - Toast.makeText(activity, R.string.error_couldNotExtractPrivateKey, - Toast.LENGTH_SHORT).show(); - return; - } - } catch (PGPException e) { - Toast.makeText(activity, R.string.wrongPassPhrase, Toast.LENGTH_SHORT) - .show(); - return; - } - keyId = secretKey.getKeyID(); - } else { - keyId = Id.key.symmetric; - } - - // cache again - PGPMain.setCachedPassPhrase(keyId, passPhrase); - // return by callback - 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); - } - }); - - // check if the key has no passphrase - if (secretKey != null) { - try { - Log.d("APG", "check if key has no passphrase..."); - PGPPrivateKey testKey = secretKey.extractPrivateKey("".toCharArray(), - new BouncyCastleProvider()); - if (testKey != null) { - Log.d("APG", "Key has no passphrase!"); - - // cache null - PGPMain.setCachedPassPhrase(secretKey.getKeyID(), null); - // return by callback - cb.passPhraseCallback(secretKey.getKeyID(), null); - - return null; - } - } catch (PGPException e) { - - } - } - return alert.create(); - } -} diff --git a/org_apg/src/org/thialfihar/android/apg/deprecated/BaseActivity.java b/org_apg/src/org/thialfihar/android/apg/deprecated/BaseActivity.java index 2d1cfcf6c..09638e478 100644 --- a/org_apg/src/org/thialfihar/android/apg/deprecated/BaseActivity.java +++ b/org_apg/src/org/thialfihar/android/apg/deprecated/BaseActivity.java @@ -42,7 +42,7 @@ import android.os.Handler; import android.os.Message; public class BaseActivity extends SherlockFragmentActivity implements Runnable, - ProgressDialogUpdater, AskForPassphrase.PassPhraseCallbackInterface { + ProgressDialogUpdater { private ProgressDialog mProgressDialog = null; // private PausableThread mRunningThread = null; @@ -363,11 +363,11 @@ public class BaseActivity extends SherlockFragmentActivity implements Runnable, // // Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); // } - - public void passPhraseCallback(long keyId, String passPhrase) { - // TODO: Not needed anymore, now implemented in AskForSecretKeyPass - PGPMain.setCachedPassPhrase(keyId, passPhrase); - } + // + // public void passPhraseCallback(long keyId, String passPhrase) { + // // TODO: Not needed anymore, now implemented in AskForSecretKeyPass + // PGPMain.setCachedPassPhrase(keyId, passPhrase); + // } // public void sendMessage(Message msg) { // mHandler.sendMessage(msg); diff --git a/org_apg/src/org/thialfihar/android/apg/helper/PGPMain.java b/org_apg/src/org/thialfihar/android/apg/helper/PGPMain.java index 2fd63bda1..887b4c8f6 100644 --- a/org_apg/src/org/thialfihar/android/apg/helper/PGPMain.java +++ b/org_apg/src/org/thialfihar/android/apg/helper/PGPMain.java @@ -78,7 +78,6 @@ 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.service.ApgService; -import org.thialfihar.android.apg.service.CachedPassphrase; import org.thialfihar.android.apg.util.HkpKeyServer; import org.thialfihar.android.apg.util.InputData; import org.thialfihar.android.apg.util.PositionAwareInputStream; @@ -121,9 +120,7 @@ import java.security.Security; import java.security.SignatureException; import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.Iterator; -import java.util.Map; import java.util.Vector; import java.util.regex.Pattern; @@ -185,7 +182,6 @@ public class PGPMain { ".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*", Pattern.DOTALL); - private static HashMap mPassPhraseCache = new HashMap(); private static String mEditPassPhrase = null; private static Database mDatabase = null; @@ -224,58 +220,6 @@ public class PGPMain { 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 = PGPHelper.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; - } - /** * Creates new secret key. The returned PGPSecretKeyRing contains only one newly generated key * when this key is the new masterkey. If a masterkey is supplied in the parameters @@ -1268,11 +1212,10 @@ public class PGPMain { progress.setProgress(R.string.progress_done, 100, 100); } - public static PGPPublicKeyRing signKey(Context context, long masterKeyId, long pubKeyId) - throws GeneralException, NoSuchAlgorithmException, NoSuchProviderException, - PGPException, SignatureException { - String signaturePassPhrase = PGPMain.getCachedPassPhrase(masterKeyId); - if (signaturePassPhrase == null || signaturePassPhrase.length() <= 0) { + public static PGPPublicKeyRing signKey(Context context, long masterKeyId, long pubKeyId, + String passphrase) throws GeneralException, NoSuchAlgorithmException, + NoSuchProviderException, PGPException, SignatureException { + if (passphrase == null || passphrase.length() <= 0) { throw new GeneralException("Unable to obtain passphrase"); } else { PGPPublicKeyRing pubring = PGPMain.getPublicKeyRing(pubKeyId); @@ -1283,7 +1226,7 @@ public class PGPMain { } PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassPhrase.toCharArray()); + BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); if (signaturePrivateKey == null) { throw new GeneralException( diff --git a/org_apg/src/org/thialfihar/android/apg/service/ApgService.java b/org_apg/src/org/thialfihar/android/apg/service/ApgService.java index 43987ccbc..624572b7b 100644 --- a/org_apg/src/org/thialfihar/android/apg/service/ApgService.java +++ b/org_apg/src/org/thialfihar/android/apg/service/ApgService.java @@ -70,6 +70,25 @@ public class ApgService extends IntentService implements ProgressDialogUpdater { public static final String EXTRA_ACTION = "action"; public static final String EXTRA_DATA = "data"; + /* possible EXTRA_ACTIONs */ + public static final int ACTION_ENCRYPT_SIGN = 10; + + public static final int ACTION_DECRYPT_VERIFY = 20; + + public static final int ACTION_SAVE_KEYRING = 30; + public static final int ACTION_GENERATE_KEY = 31; + public static final int ACTION_GENERATE_DEFAULT_RSA_KEYS = 32; + + public static final int ACTION_DELETE_FILE_SECURELY = 40; + + public static final int ACTION_IMPORT_KEY = 50; + public static final int ACTION_EXPORT_KEY = 51; + + public static final int ACTION_UPLOAD_KEY = 60; + public static final int ACTION_QUERY_KEY = 61; + + public static final int ACTION_SIGN_KEY = 70; + /* keys for data bundle */ // encrypt, decrypt, import export @@ -143,25 +162,6 @@ public class ApgService extends IntentService implements ProgressDialogUpdater { public static final String SIGN_KEY_MASTER_KEY_ID = "signKeyMasterKeyId"; public static final String SIGN_KEY_PUB_KEY_ID = "signKeyPubKeyId"; - /* possible EXTRA_ACTIONs */ - public static final int ACTION_ENCRYPT_SIGN = 10; - - public static final int ACTION_DECRYPT_VERIFY = 20; - - public static final int ACTION_SAVE_KEYRING = 30; - public static final int ACTION_GENERATE_KEY = 31; - public static final int ACTION_GENERATE_DEFAULT_RSA_KEYS = 32; - - public static final int ACTION_DELETE_FILE_SECURELY = 40; - - public static final int ACTION_IMPORT_KEY = 50; - public static final int ACTION_EXPORT_KEY = 51; - - public static final int ACTION_UPLOAD_KEY = 60; - public static final int ACTION_QUERY_KEY = 61; - - public static final int ACTION_SIGN_KEY = 70; - /* possible data keys as result send over messenger */ // keys public static final String RESULT_NEW_KEY = "newKey"; @@ -320,19 +320,21 @@ public class ApgService extends IntentService implements ProgressDialogUpdater { if (generateSignature) { Log.d(Constants.TAG, "generating signature..."); PGPMain.generateSignature(this, inputData, outStream, useAsciiArmour, false, - secretKeyId, PGPMain.getCachedPassPhrase(secretKeyId), Preferences - .getPreferences(this).getDefaultHashAlgorithm(), Preferences - .getPreferences(this).getForceV3Signatures(), this); + secretKeyId, PassphraseCacheService.getCachedPassphrase(this, + secretKeyId), Preferences.getPreferences(this) + .getDefaultHashAlgorithm(), Preferences.getPreferences(this) + .getForceV3Signatures(), this); } else if (signOnly) { Log.d(Constants.TAG, "sign only..."); - PGPMain.signText(this, inputData, outStream, secretKeyId, PGPMain - .getCachedPassPhrase(secretKeyId), Preferences.getPreferences(this) - .getDefaultHashAlgorithm(), Preferences.getPreferences(this) - .getForceV3Signatures(), this); + PGPMain.signText(this, inputData, outStream, secretKeyId, + PassphraseCacheService.getCachedPassphrase(this, secretKeyId), + Preferences.getPreferences(this).getDefaultHashAlgorithm(), Preferences + .getPreferences(this).getForceV3Signatures(), this); } else { Log.d(Constants.TAG, "encrypt..."); PGPMain.encrypt(this, inputData, outStream, useAsciiArmour, encryptionKeyIds, - signatureKeyId, PGPMain.getCachedPassPhrase(signatureKeyId), this, + signatureKeyId, + PassphraseCacheService.getCachedPassphrase(this, signatureKeyId), this, Preferences.getPreferences(this).getDefaultEncryptionAlgorithm(), Preferences.getPreferences(this).getDefaultHashAlgorithm(), compressionId, Preferences.getPreferences(this).getForceV3Signatures(), @@ -478,7 +480,7 @@ public class ApgService extends IntentService implements ProgressDialogUpdater { this); } else { resultData = PGPMain.decrypt(this, inputData, outStream, - PGPMain.getCachedPassPhrase(secretKeyId), this, + PassphraseCacheService.getCachedPassphrase(this, secretKeyId), this, assumeSymmetricEncryption); } @@ -539,7 +541,7 @@ public class ApgService extends IntentService implements ProgressDialogUpdater { /* Operation */ PGPMain.buildSecretKey(this, userIds, keys, keysUsages, masterKeyId, oldPassPhrase, newPassPhrase, this); - PGPMain.setCachedPassPhrase(masterKeyId, newPassPhrase); + PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase); /* Output */ sendMessageToHandler(ApgServiceHandler.MESSAGE_OKAY); @@ -798,7 +800,11 @@ public class ApgService extends IntentService implements ProgressDialogUpdater { long pubKeyId = data.getLong(SIGN_KEY_PUB_KEY_ID); /* Operation */ - PGPPublicKeyRing signedPubKeyRing = PGPMain.signKey(this, masterKeyId, pubKeyId); + String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this, + masterKeyId); + + PGPPublicKeyRing signedPubKeyRing = PGPMain.signKey(this, masterKeyId, pubKeyId, + signaturePassPhrase); // store the signed key in our local cache int retval = PGPMain.storeKeyRingInCache(signedPubKeyRing); diff --git a/org_apg/src/org/thialfihar/android/apg/service/CachedPassphrase.java b/org_apg/src/org/thialfihar/android/apg/service/CachedPassphrase.java index 0467bc2c5..0631bd6fa 100644 --- a/org_apg/src/org/thialfihar/android/apg/service/CachedPassphrase.java +++ b/org_apg/src/org/thialfihar/android/apg/service/CachedPassphrase.java @@ -15,19 +15,27 @@ package org.thialfihar.android.apg.service; public class CachedPassphrase { - public final long timestamp; - public final String passPhrase; + private final long timestamp; + private final String passphrase; public CachedPassphrase(long timestamp, String passPhrase) { super(); this.timestamp = timestamp; - this.passPhrase = passPhrase; + this.passphrase = passPhrase; + } + + public long getTimestamp() { + return timestamp; + } + + public String getPassphrase() { + return passphrase; } @Override public int hashCode() { int hc1 = (int) (this.timestamp & 0xffffffff); - int hc2 = (this.passPhrase == null ? 0 : this.passPhrase.hashCode()); + int hc2 = (this.passphrase == null ? 0 : this.passphrase.hashCode()); return (hc1 + hc2) * hc2 + hc1; } @@ -42,12 +50,12 @@ public class CachedPassphrase { return false; } - if (passPhrase != o.passPhrase) { - if (passPhrase == null || o.passPhrase == null) { + if (passphrase != o.passphrase) { + if (passphrase == null || o.passphrase == null) { return false; } - if (!passPhrase.equals(o.passPhrase)) { + if (!passphrase.equals(o.passphrase)) { return false; } } diff --git a/org_apg/src/org/thialfihar/android/apg/service/PassphraseCacheService.java b/org_apg/src/org/thialfihar/android/apg/service/PassphraseCacheService.java index 4d004ebfd..1a843b3fd 100644 --- a/org_apg/src/org/thialfihar/android/apg/service/PassphraseCacheService.java +++ b/org_apg/src/org/thialfihar/android/apg/service/PassphraseCacheService.java @@ -1,4 +1,6 @@ /* + * Copyright (C) 2012 Dominik Schürmann + * * 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 @@ -14,90 +16,211 @@ package org.thialfihar.android.apg.service; +import java.util.HashMap; + +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.helper.PGPHelper; import org.thialfihar.android.apg.helper.PGPMain; import org.thialfihar.android.apg.helper.Preferences; +import android.app.AlarmManager; +import android.app.PendingIntent; import android.app.Service; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.os.Binder; -import android.os.Handler; import android.os.IBinder; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; public class PassphraseCacheService extends Service { - private final IBinder mBinder = new LocalBinder(); + public static final String BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE = Constants.INTENT_PREFIX + + "PASSPHRASE_CACHE_SERVICE"; public static final String EXTRA_TTL = "ttl"; + public static final String EXTRA_KEY_ID = "keyId"; + public static final String EXTRA_PASSPHRASE = "passphrase"; + + private static final int REQUEST_ID = 0; + private static final long DEFAULT_TTL = 15; + + private BroadcastReceiver mIntentReceiver; + + // TODO: This is static to be easily retrieved by getCachedPassphrase() + // To avoid static we would need a messenger from the service back to the activity? + private static HashMap mPassphraseCache = new HashMap(); + + /** + * This caches a new passphrase by sending a new command to the service. An android service is + * only run once. Thus when it is already started new commands just add new BroadcastReceivers + * for cached passphrases + * + * @param context + * @param keyId + * @param passphrase + */ + public static void addCachedPassphrase(Context context, long keyId, String passphrase) { + Log.d(Constants.TAG, "cacheNewPassphrase() for " + keyId); - public static void startCacheService(Context context) { Intent intent = new Intent(context, PassphraseCacheService.class); - intent.putExtra(PassphraseCacheService.EXTRA_TTL, Preferences.getPreferences(context).getPassPhraseCacheTtl()); + intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassPhraseCacheTtl()); + intent.putExtra(EXTRA_PASSPHRASE, passphrase); + intent.putExtra(EXTRA_KEY_ID, keyId); + context.startService(intent); } - 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; + /** + * Gets a cached passphrase from memory + * + * @param context + * @param keyId + * @return + */ + public static String getCachedPassphrase(Context context, long keyId) { + // try to get real key id + long realId = keyId; + if (realId != Id.key.symmetric) { + PGPSecretKeyRing keyRing = PGPMain.getSecretKeyRing(keyId); + if (keyRing == null) { + return null; } - - delay = PGPMain.cleanUpCache(mPassPhraseCacheTtl, delay); - // don't check too often, even if we were close - if (delay < 5000) { - delay = 5000; + PGPSecretKey masterKey = PGPHelper.getMasterKey(keyRing); + if (masterKey == null) { + return null; } + realId = masterKey.getKeyID(); + } - mCacheHandler.postDelayed(this, delay); + // get cached passphrase + CachedPassphrase cpp = mPassphraseCache.get(realId); + if (cpp == null) { + return null; } - }; + // set it again to reset the cache life cycle + addCachedPassphrase(context, realId, cpp.getPassphrase()); - static private boolean mIsRunning = false; + return cpp.getPassphrase(); + } - @Override - public void onCreate() { - super.onCreate(); + /** + * Register BroadcastReceiver that is unregistered when service is destroyed. This + * BroadcastReceiver hears on intents with ACTION_PASSPHRASE_CACHE_SERVICE to timeout + * passphrases in memory. + */ + private void registerReceiver() { + if (mIntentReceiver == null) { + mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (action.equals(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE)) { + long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1); + timeout(context, keyId); + } + } + }; + + IntentFilter filter = new IntentFilter(); + filter.addAction(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE); + LocalBroadcastManager.getInstance(this).registerReceiver(mIntentReceiver, filter); + } + } - mIsRunning = true; + /** + * Build pending intent that is executed by alarm manager when one passphrase times out + * + * @param context + * @param keyId + * @return + */ + private static PendingIntent buildIntent(Context context, long keyId) { + Intent intent = new Intent(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE); + intent.putExtra(EXTRA_KEY_ID, keyId); + PendingIntent sender = PendingIntent.getBroadcast(context, REQUEST_ID, intent, + PendingIntent.FLAG_CANCEL_CURRENT); + + return sender; } @Override - public void onDestroy() { - super.onDestroy(); - mIsRunning = false; + public void onCreate() { + Log.d(Constants.TAG, "PassphraseCacheService created!"); } + /** + * Executed when service is started by intent + */ @Override - public void onStart(Intent intent, int startId) { - super.onStart(intent, startId); + public int onStartCommand(Intent intent, int flags, int startId) { + Log.d(Constants.TAG, "PassphraseCacheService started"); + + // register broadcastreceiver + registerReceiver(); if (intent != null) { - mPassPhraseCacheTtl = intent.getIntExtra(EXTRA_TTL, 15); + long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL); + long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1); + String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE); + + Log.d(Constants.TAG, "received intent with keyId: " + keyId + ", ttl: " + ttl); + + // add keyId and passphrase to memory + mPassphraseCache.put(keyId, + new CachedPassphrase(System.currentTimeMillis(), passphrase)); + + // register new alarm with keyId for this passphrase + long triggerTime = System.currentTimeMillis() + ttl; + AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); + am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, keyId)); } - if (mPassPhraseCacheTtl < 15) { - mPassPhraseCacheTtl = 15; + + return START_STICKY; + } + + /** + * Called when one specific passphrase for keyId timed out + * + * @param context + * @param keyId + */ + private void timeout(Context context, long keyId) { + Log.d(Constants.TAG, "Timeout of " + keyId); + + // remove passphrase corresponding to keyId from memory + mPassphraseCache.remove(keyId); + + // stop whole service if no cached passphrases remaining + if (mPassphraseCache.isEmpty()) { + Log.d(Constants.TAG, "No passphrases remaining in memory, stopping service!"); + stopSelf(); } - mCacheHandler.removeCallbacks(mCacheTask); - mCacheHandler.postDelayed(mCacheTask, 1000); } - static public boolean isRunning() { - return mIsRunning; + @Override + public void onDestroy() { + Log.d(Constants.TAG, "PassphraseCacheService destroyed!"); + + LocalBroadcastManager.getInstance(this).unregisterReceiver(mIntentReceiver); } - public class LocalBinder extends Binder { - PassphraseCacheService getService() { + public class PassphraseCacheBinder extends Binder { + public PassphraseCacheService getService() { return PassphraseCacheService.this; } } + private final IBinder mBinder = new PassphraseCacheBinder(); + @Override public IBinder onBind(Intent intent) { return mBinder; } -} + +} \ No newline at end of file diff --git a/org_apg/src/org/thialfihar/android/apg/ui/DecryptActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/DecryptActivity.java index 40f2b3f43..b2463c5ac 100644 --- a/org_apg/src/org/thialfihar/android/apg/ui/DecryptActivity.java +++ b/org_apg/src/org/thialfihar/android/apg/ui/DecryptActivity.java @@ -25,6 +25,7 @@ import org.thialfihar.android.apg.helper.PGPHelper; import org.thialfihar.android.apg.helper.PGPMain; import org.thialfihar.android.apg.service.ApgServiceHandler; import org.thialfihar.android.apg.service.ApgService; +import org.thialfihar.android.apg.service.PassphraseCacheService; import org.thialfihar.android.apg.ui.dialog.DeleteFileDialogFragment; import org.thialfihar.android.apg.ui.dialog.FileDialogFragment; import org.thialfihar.android.apg.ui.dialog.LookupUnknownKeyDialogFragment; @@ -513,7 +514,7 @@ public class DecryptActivity extends SherlockFragmentActivity { // if we need a symmetric passphrase or a passphrase to use a secret key ask for it if (getSecretKeyId() == Id.key.symmetric - || PGPMain.getCachedPassPhrase(getSecretKeyId()) == null) { + || PassphraseCacheService.getCachedPassphrase(this, getSecretKeyId()) == null) { showPassphraseDialog(); } else { if (mDecryptTarget == Id.target.file) { @@ -548,7 +549,7 @@ public class DecryptActivity extends SherlockFragmentActivity { Messenger messenger = new Messenger(returnHandler); try { - PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance( + PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this, messenger, mSecretKeyId); passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); diff --git a/org_apg/src/org/thialfihar/android/apg/ui/EncryptActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/EncryptActivity.java index 4e5fda3db..831f33273 100644 --- a/org_apg/src/org/thialfihar/android/apg/ui/EncryptActivity.java +++ b/org_apg/src/org/thialfihar/android/apg/ui/EncryptActivity.java @@ -30,6 +30,7 @@ import org.thialfihar.android.apg.helper.PGPMain; import org.thialfihar.android.apg.helper.Preferences; import org.thialfihar.android.apg.service.ApgServiceHandler; import org.thialfihar.android.apg.service.ApgService; +import org.thialfihar.android.apg.service.PassphraseCacheService; import org.thialfihar.android.apg.ui.dialog.DeleteFileDialogFragment; import org.thialfihar.android.apg.ui.dialog.FileDialogFragment; import org.thialfihar.android.apg.ui.dialog.PassphraseDialogFragment; @@ -659,7 +660,8 @@ public class EncryptActivity extends SherlockFragmentActivity { return; } - if (getSecretKeyId() != 0 && PGPMain.getCachedPassPhrase(getSecretKeyId()) == null) { + if (getSecretKeyId() != 0 + && PassphraseCacheService.getCachedPassphrase(this, getSecretKeyId()) == null) { showPassphraseDialog(); return; @@ -697,7 +699,7 @@ public class EncryptActivity extends SherlockFragmentActivity { try { PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance( - messenger, mSecretKeyId); + EncryptActivity.this, messenger, mSecretKeyId); passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); } catch (PGPMain.GeneralException e) { diff --git a/org_apg/src/org/thialfihar/android/apg/ui/PreferencesActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/PreferencesActivity.java index 81a0545d1..b10667a40 100644 --- a/org_apg/src/org/thialfihar/android/apg/ui/PreferencesActivity.java +++ b/org_apg/src/org/thialfihar/android/apg/ui/PreferencesActivity.java @@ -69,7 +69,8 @@ public class PreferencesActivity extends SherlockPreferenceActivity { mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString())); // restart cache service with new ttl - PassphraseCacheService.startCacheService(PreferencesActivity.this); + // TODO: not needed anymore! + // PassphraseCacheService.startCacheService(PreferencesActivity.this); return false; } }); diff --git a/org_apg/src/org/thialfihar/android/apg/ui/SecretKeyListActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/SecretKeyListActivity.java index d7c272cf7..de799e2ac 100644 --- a/org_apg/src/org/thialfihar/android/apg/ui/SecretKeyListActivity.java +++ b/org_apg/src/org/thialfihar/android/apg/ui/SecretKeyListActivity.java @@ -21,6 +21,7 @@ import org.thialfihar.android.apg.Constants; import org.thialfihar.android.apg.Id; import org.thialfihar.android.apg.helper.PGPHelper; import org.thialfihar.android.apg.helper.PGPMain; +import org.thialfihar.android.apg.service.PassphraseCacheService; import org.thialfihar.android.apg.ui.dialog.PassphraseDialogFragment; import org.thialfihar.android.apg.util.Log; @@ -137,7 +138,7 @@ public class SecretKeyListActivity extends KeyListActivity implements OnChildCli public void checkPassPhraseAndEdit() { long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem); - String passPhrase = PGPMain.getCachedPassPhrase(keyId); + String passPhrase = PassphraseCacheService.getCachedPassphrase(this, keyId); if (passPhrase == null) { showPassphraseDialog(keyId); } else { @@ -152,7 +153,8 @@ public class SecretKeyListActivity extends KeyListActivity implements OnChildCli @Override public void handleMessage(Message message) { if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - String passPhrase = PGPMain.getCachedPassPhrase(secretKeyId); + String passPhrase = PassphraseCacheService.getCachedPassphrase( + SecretKeyListActivity.this, secretKeyId); PGPMain.setEditPassPhrase(passPhrase); editKey(); } @@ -164,7 +166,7 @@ public class SecretKeyListActivity extends KeyListActivity implements OnChildCli try { PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance( - messenger, secretKeyId); + SecretKeyListActivity.this, messenger, secretKeyId); passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); } catch (PGPMain.GeneralException e) { diff --git a/org_apg/src/org/thialfihar/android/apg/ui/SignKeyActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/SignKeyActivity.java index 4c8c12bef..48c669aa0 100644 --- a/org_apg/src/org/thialfihar/android/apg/ui/SignKeyActivity.java +++ b/org_apg/src/org/thialfihar/android/apg/ui/SignKeyActivity.java @@ -27,6 +27,7 @@ import org.thialfihar.android.apg.helper.PGPMain; import org.thialfihar.android.apg.helper.Preferences; import org.thialfihar.android.apg.service.ApgService; import org.thialfihar.android.apg.service.ApgServiceHandler; +import org.thialfihar.android.apg.service.PassphraseCacheService; import org.thialfihar.android.apg.ui.dialog.PassphraseDialogFragment; import com.actionbarsherlock.app.ActionBar; @@ -141,7 +142,7 @@ public class SignKeyActivity extends SherlockFragmentActivity { Messenger messenger = new Messenger(returnHandler); try { - PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance( + PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this, messenger, secretKeyId); passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); @@ -175,7 +176,7 @@ public class SignKeyActivity extends SherlockFragmentActivity { /* * get the user's passphrase for this key (if required) */ - String passphrase = PGPMain.getCachedPassPhrase(mMasterKeyId); + String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId); if (passphrase == null) { showPassphraseDialog(mMasterKeyId); return; // bail out; need to wait until the user has entered the passphrase diff --git a/org_apg/src/org/thialfihar/android/apg/ui/dialog/PassphraseDialogFragment.java b/org_apg/src/org/thialfihar/android/apg/ui/dialog/PassphraseDialogFragment.java index 67d4e94c0..e8ef76e50 100644 --- a/org_apg/src/org/thialfihar/android/apg/ui/dialog/PassphraseDialogFragment.java +++ b/org_apg/src/org/thialfihar/android/apg/ui/dialog/PassphraseDialogFragment.java @@ -31,6 +31,7 @@ import org.thialfihar.android.apg.R; 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.os.Bundle; @@ -38,13 +39,22 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.DialogFragment; + +import org.thialfihar.android.apg.service.PassphraseCacheService; import org.thialfihar.android.apg.util.Log; + +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; +import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; import android.widget.EditText; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; import android.widget.Toast; -public class PassphraseDialogFragment extends DialogFragment { +public class PassphraseDialogFragment extends DialogFragment implements OnEditorActionListener { private Messenger mMessenger; @@ -53,6 +63,8 @@ public class PassphraseDialogFragment extends DialogFragment { public static final int MESSAGE_OKAY = 1; + private EditText mPassphraseEditText; + /** * Creates new instance of this dialog fragment * @@ -63,11 +75,11 @@ public class PassphraseDialogFragment extends DialogFragment { * @return * @throws GeneralException */ - public static PassphraseDialogFragment newInstance(Messenger messenger, long secretKeyId) - throws GeneralException { + public static PassphraseDialogFragment newInstance(Context context, Messenger messenger, + long secretKeyId) throws GeneralException { // check if secret key has a passphrase if (!(secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none)) { - if (!hasPassphrase(secretKeyId)) { + if (!hasPassphrase(context, secretKeyId)) { throw new PGPMain.GeneralException("No passphrase! No passphrase dialog needed!"); } } @@ -88,7 +100,7 @@ public class PassphraseDialogFragment extends DialogFragment { * @param secretKeyId * @return true if it has a passphrase */ - private static boolean hasPassphrase(long secretKeyId) { + private static boolean hasPassphrase(Context context, long secretKeyId) { // check if the key has no passphrase try { PGPSecretKey secretKey = PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId)); @@ -101,7 +113,7 @@ public class PassphraseDialogFragment extends DialogFragment { Log.d(Constants.TAG, "Key has no passphrase! Caches empty passphrase!"); // cache empty passphrase - PGPMain.setCachedPassPhrase(secretKey.getKeyID(), ""); + PassphraseCacheService.addCachedPassphrase(context, secretKey.getKeyID(), ""); return false; } @@ -112,6 +124,11 @@ public class PassphraseDialogFragment extends DialogFragment { return true; } + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + /** * Creates dialog */ @@ -154,13 +171,13 @@ public class PassphraseDialogFragment extends DialogFragment { View view = inflater.inflate(R.layout.passphrase, null); alert.setView(view); - final EditText input = (EditText) view.findViewById(R.id.passphrase_passphrase); + mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase); alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dismiss(); - String passPhrase = input.getText().toString(); + String passPhrase = mPassphraseEditText.getText().toString(); long keyId; if (secretKey != null) { try { @@ -185,7 +202,7 @@ public class PassphraseDialogFragment extends DialogFragment { // cache the new passphrase Log.d(Constants.TAG, "Everything okay! Caching entered passphrase"); - PGPMain.setCachedPassPhrase(keyId, passPhrase); + PassphraseCacheService.addCachedPassphrase(activity, keyId, passPhrase); sendMessageToHandler(MESSAGE_OKAY); } @@ -200,6 +217,32 @@ public class PassphraseDialogFragment extends DialogFragment { return alert.create(); } + @Override + public void onActivityCreated(Bundle arg0) { + super.onActivityCreated(arg0); + + // request focus and open soft keyboard + mPassphraseEditText.requestFocus(); + getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE); + + mPassphraseEditText.setOnEditorActionListener(this); + } + + /** + * Associate the "done" button on the soft keyboard with the okay button in the view + */ + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (EditorInfo.IME_ACTION_DONE == actionId) { + AlertDialog dialog = ((AlertDialog) getDialog()); + Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE); + + bt.performClick(); + return true; + } + return false; + } + /** * Send message back to handler which is initialized in a activity * @@ -218,4 +261,5 @@ public class PassphraseDialogFragment extends DialogFragment { Log.w(Constants.TAG, "Messenger is null!", e); } } + } \ No newline at end of file diff --git a/org_apg/src/org/thialfihar/android/apg/ui/dialog/SetPassphraseDialogFragment.java b/org_apg/src/org/thialfihar/android/apg/ui/dialog/SetPassphraseDialogFragment.java index 849f3428b..b23101a0a 100644 --- a/org_apg/src/org/thialfihar/android/apg/ui/dialog/SetPassphraseDialogFragment.java +++ b/org_apg/src/org/thialfihar/android/apg/ui/dialog/SetPassphraseDialogFragment.java @@ -29,12 +29,19 @@ import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.DialogFragment; import org.thialfihar.android.apg.util.Log; + +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; +import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; import android.widget.EditText; +import android.widget.TextView; import android.widget.Toast; +import android.widget.TextView.OnEditorActionListener; -public class SetPassphraseDialogFragment extends DialogFragment { +public class SetPassphraseDialogFragment extends DialogFragment implements OnEditorActionListener { private Messenger mMessenger; private static final String ARG_MESSENGER = "messenger"; @@ -44,6 +51,9 @@ public class SetPassphraseDialogFragment extends DialogFragment { public static final String MESSAGE_NEW_PASSPHRASE = "new_passphrase"; + private EditText mPassphraseEditText; + private EditText mPassphraseAgainEditText; + /** * Creates new instance of this dialog fragment * @@ -81,17 +91,17 @@ public class SetPassphraseDialogFragment extends DialogFragment { LayoutInflater inflater = activity.getLayoutInflater(); View view = inflater.inflate(R.layout.passphrase_repeat, null); - final EditText input1 = (EditText) view.findViewById(R.id.passphrase_passphrase); - final EditText input2 = (EditText) view.findViewById(R.id.passphrase_passphrase_again); - alert.setView(view); + mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase); + mPassphraseAgainEditText = (EditText) view.findViewById(R.id.passphrase_passphrase_again); + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dismiss(); - String passPhrase1 = input1.getText().toString(); - String passPhrase2 = input2.getText().toString(); + String passPhrase1 = mPassphraseEditText.getText().toString(); + String passPhrase2 = mPassphraseAgainEditText.getText().toString(); if (!passPhrase1.equals(passPhrase2)) { Toast.makeText( activity, @@ -127,6 +137,32 @@ public class SetPassphraseDialogFragment extends DialogFragment { return alert.create(); } + @Override + public void onActivityCreated(Bundle arg0) { + super.onActivityCreated(arg0); + + // request focus and open soft keyboard + mPassphraseEditText.requestFocus(); + getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE); + + mPassphraseAgainEditText.setOnEditorActionListener(this); + } + + /** + * Associate the "done" button on the soft keyboard with the okay button in the view + */ + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (EditorInfo.IME_ACTION_DONE == actionId) { + AlertDialog dialog = ((AlertDialog) getDialog()); + Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE); + + bt.performClick(); + return true; + } + return false; + } + /** * Send message back to handler which is initialized in a activity * diff --git a/org_apg/src/org/thialfihar/android/apg/ui/widget/SectionView.java b/org_apg/src/org/thialfihar/android/apg/ui/widget/SectionView.java index 140ba3a4f..f14dedbfc 100644 --- a/org_apg/src/org/thialfihar/android/apg/ui/widget/SectionView.java +++ b/org_apg/src/org/thialfihar/android/apg/ui/widget/SectionView.java @@ -23,6 +23,7 @@ import org.thialfihar.android.apg.helper.PGPMain; import org.thialfihar.android.apg.helper.PGPConversionHelper; import org.thialfihar.android.apg.service.ApgServiceHandler; import org.thialfihar.android.apg.service.ApgService; +import org.thialfihar.android.apg.service.PassphraseCacheService; import org.thialfihar.android.apg.ui.dialog.ProgressDialogFragment; import org.thialfihar.android.apg.ui.widget.Editor.EditorListener; import org.thialfihar.android.apg.util.Choice; @@ -259,7 +260,8 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor String passPhrase; if (mEditors.getChildCount() > 0) { PGPSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue(); - passPhrase = PGPMain.getCachedPassPhrase(masterKey.getKeyID()); + passPhrase = PassphraseCacheService + .getCachedPassphrase(mActivity, masterKey.getKeyID()); data.putByteArray(ApgService.MASTER_KEY, PGPConversionHelper.PGPSecretKeyToBytes(masterKey)); diff --git a/org_apg/src/org/thialfihar/android/apg/util/KeyServer.java b/org_apg/src/org/thialfihar/android/apg/util/KeyServer.java index 5faa5a3df..97c6c7636 100644 --- a/org_apg/src/org/thialfihar/android/apg/util/KeyServer.java +++ b/org_apg/src/org/thialfihar/android/apg/util/KeyServer.java @@ -20,7 +20,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Vector; import android.os.Parcel; import android.os.Parcelable; -- cgit v1.2.3