diff options
author | Vincent Breitmoser <valodim@mugenguild.com> | 2014-03-12 23:45:21 +0100 |
---|---|---|
committer | Vincent Breitmoser <valodim@mugenguild.com> | 2014-03-12 23:45:21 +0100 |
commit | a9c9b6132c7b122f8155ce9fc6c21c89e5b8c298 (patch) | |
tree | 78671569d3e84f5ed3db638a194981bc7c053a88 /OpenPGP-Keychain/src/main/java | |
parent | edb98c67f4031b8c3c1d43b49bba733171119be2 (diff) | |
parent | 69f5bf6b577234053e700a43a4a7ba721e827c6a (diff) | |
download | open-keychain-a9c9b6132c7b122f8155ce9fc6c21c89e5b8c298.tar.gz open-keychain-a9c9b6132c7b122f8155ce9fc6c21c89e5b8c298.tar.bz2 open-keychain-a9c9b6132c7b122f8155ce9fc6c21c89e5b8c298.zip |
Merge branch 'master' into certs
Conflicts:
OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
Diffstat (limited to 'OpenPGP-Keychain/src/main/java')
59 files changed, 1797 insertions, 904 deletions
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index c1809e4e1..34a3de8d5 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -43,6 +43,8 @@ public final class Constants { public static final class path { public static final String APP_DIR = Environment.getExternalStorageDirectory() + "/OpenPGP-Keychain"; + public static final String APP_DIR_FILE_SEC = APP_DIR + "/secexport.asc"; + public static final String APP_DIR_FILE_PUB = APP_DIR + "/pubexport.asc"; } public static final class pref { @@ -51,7 +53,7 @@ public final class Constants { 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"; + public static final String PASS_PHRASE_CACHE_TTL = "passphraseCacheTtl"; public static final String LANGUAGE = "language"; public static final String FORCE_V3_SIGNATURES = "forceV3Signatures"; public static final String KEY_SERVERS = "keyServers"; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java index a1571e491..1d79edd43 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java @@ -30,7 +30,7 @@ public final class Id { public static final class menu { public static final class option { - public static final int new_pass_phrase = 0x21070001; + public static final int new_passphrase = 0x21070001; public static final int create = 0x21070002; public static final int about = 0x21070003; public static final int manage_public_keys = 0x21070004; @@ -85,12 +85,12 @@ public final class Id { } public static final class dialog { - public static final int pass_phrase = 0x21070001; + public static final int passphrase = 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 new_passphrase = 0x21070004; + public static final int passphrases_do_not_match = 0x21070005; + public static final int no_passphrase = 0x21070006; public static final int saving = 0x21070007; public static final int delete_key = 0x21070008; public static final int import_keys = 0x21070009; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java index 032af4c71..3164de7d1 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java @@ -59,7 +59,6 @@ public class ClipboardReflection { * Wrapper around ClipboardManager based on Android version using Reflection API * * @param context - * @param text */ public static CharSequence getClipboardText(Context context) { Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java index b55075e6c..24e8ff4c7 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java @@ -56,28 +56,33 @@ public class ActionBarHelper { * Sets custom view on ActionBar for Done/Cancel activities * * @param actionBar - * @param doneText - * @param doneOnClickListener - * @param cancelText - * @param cancelOnClickListener + * @param firstText + * @param firstDrawableId + * @param firstOnClickListener + * @param secondText + * @param secondDrawableId + * @param secondOnClickListener */ - public static void setDoneCancelView(ActionBar actionBar, int doneText, - OnClickListener doneOnClickListener, int cancelText, - OnClickListener cancelOnClickListener) { + public static void setTwoButtonView(ActionBar actionBar, int firstText, int firstDrawableId, + OnClickListener firstOnClickListener, int secondText, int secondDrawableId, + OnClickListener secondOnClickListener) { - // Inflate a "Done"/"Cancel" custom action bar view + // Inflate the custom action bar view final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext() .getSystemService(Activity.LAYOUT_INFLATER_SERVICE); final View customActionBarView = inflater.inflate( R.layout.actionbar_custom_view_done_cancel, null); - ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)).setText(doneText); + TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)); + firstTextView.setText(firstText); + firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0); customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener( - doneOnClickListener); - ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text)) - .setText(cancelText); + firstOnClickListener); + TextView secondTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text)); + secondTextView.setText(secondText); + secondTextView.setCompoundDrawablesWithIntrinsicBounds(secondDrawableId, 0, 0, 0); customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener( - cancelOnClickListener); + secondOnClickListener); // Show the custom action bar view and hide the normal Home icon and title. actionBar.setDisplayShowTitleEnabled(false); @@ -91,20 +96,22 @@ public class ActionBarHelper { * Sets custom view on ActionBar for Done activities * * @param actionBar - * @param doneText - * @param doneOnClickListener + * @param firstText + * @param firstOnClickListener */ - public static void setDoneView(ActionBar actionBar, int doneText, - OnClickListener doneOnClickListener) { + public static void setOneButtonView(ActionBar actionBar, int firstText, int firstDrawableId, + OnClickListener firstOnClickListener) { // Inflate a "Done" custom action bar view to serve as the "Up" affordance. final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext() .getSystemService(Activity.LAYOUT_INFLATER_SERVICE); final View customActionBarView = inflater .inflate(R.layout.actionbar_custom_view_done, null); - ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)).setText(doneText); + TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)); + firstTextView.setText(firstText); + firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0); customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener( - doneOnClickListener); + firstOnClickListener); // Show the custom action bar view and hide the normal Home icon and title. actionBar.setDisplayShowTitleEnabled(false); @@ -112,5 +119,4 @@ public class ActionBarHelper { actionBar.setDisplayShowCustomEnabled(true); actionBar.setCustomView(customActionBarView); } - } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java new file mode 100644 index 000000000..f8ed21816 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.helper; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.Context; +import android.util.Patterns; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class ContactHelper { + + public static final List<String> getMailAccounts(Context context) { + final Account[] accounts = AccountManager.get(context).getAccounts(); + final Set<String> emailSet = new HashSet<String>(); + for (Account account : accounts) { + if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) { + emailSet.add(account.name); + } + } + return new ArrayList<String>(emailSet); + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java index 21676a2a7..19dfccbde 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java @@ -20,7 +20,6 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; @@ -63,7 +62,7 @@ public class ExportHelper { /** * Show dialog where to export keys */ - public void showExportKeysDialog(final Uri dataUri, final int keyType, + public void showExportKeysDialog(final long[] rowIds, final int keyType, final String exportFilename) { mExportFilename = exportFilename; @@ -75,7 +74,7 @@ public class ExportHelper { Bundle data = message.getData(); mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - exportKeys(dataUri, keyType); + exportKeys(rowIds, keyType); } } }; @@ -86,7 +85,7 @@ public class ExportHelper { DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { String title = null; - if (dataUri == null) { + if (rowIds == null) { // export all keys title = activity.getString(R.string.title_export_keys); } else { @@ -112,7 +111,7 @@ public class ExportHelper { /** * Export keys */ - public void exportKeys(Uri dataUri, int keyType) { + public void exportKeys(long[] rowIds, int keyType) { Log.d(Constants.TAG, "exportKeys started"); // Send all information needed to service to export key in other thread @@ -126,20 +125,17 @@ public class ExportHelper { data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename); data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType); - if (dataUri == null) { + if (rowIds == null) { data.putBoolean(KeychainIntentService.EXPORT_ALL, true); } else { - // TODO: put data uri into service??? - long keyRingMasterKeyId = ProviderHelper.getMasterKeyId(activity, dataUri); - - data.putLong(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, keyRingMasterKeyId); + data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_ROW_ID, rowIds); } intent.putExtra(KeychainIntentService.EXTRA_DATA, data); // Message is received after exporting is done in ApgService KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(activity, - R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) { + activity.getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { // handle messages by standard ApgHandler first super.handleMessage(message); @@ -160,7 +156,7 @@ public class ExportHelper { Toast.makeText(activity, toastMessage, Toast.LENGTH_SHORT).show(); } - }; + } }; // Create a new Messenger for the communication back diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java index 639ab17b8..34d90a17f 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java @@ -17,8 +17,6 @@ package org.sufficientlysecure.keychain.helper; -import java.util.Calendar; -import java.util.GregorianCalendar; import java.util.Iterator; import java.util.Set; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java index 493f25707..f18a290b3 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java @@ -144,8 +144,8 @@ public class Preferences { Constants.defaults.KEY_SERVERS); Vector<String> servers = new Vector<String>(); String chunks[] = rawData.split(","); - for (int i = 0; i < chunks.length; ++i) { - String tmp = chunks[i].trim(); + for (String c : chunks) { + String tmp = c.trim(); if (tmp.length() > 0) { servers.add(tmp); } @@ -156,8 +156,8 @@ public class Preferences { public void setKeyServers(String[] value) { SharedPreferences.Editor editor = mSharedPreferences.edit(); String rawData = ""; - for (int i = 0; i < value.length; ++i) { - String tmp = value[i].trim(); + for (String v : value) { + String tmp = v.trim(); if (tmp.length() == 0) { continue; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java index ca97cbd9f..b268de3a6 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java @@ -78,7 +78,7 @@ public class PgpConversionHelper { * * Singles keys are encoded as keyRings with one single key in it by Bouncy Castle * - * @param keysBytes + * @param keyBytes * @return */ public static PGPSecretKey BytesToPGPSecretKey(byte[] keyBytes) { @@ -149,7 +149,7 @@ public class PgpConversionHelper { /** * Convert from PGPSecretKey to byte[] * - * @param keysBytes + * @param key * @return */ public static byte[] PGPSecretKeyToBytes(PGPSecretKey key) { @@ -165,7 +165,7 @@ public class PgpConversionHelper { /** * Convert from PGPSecretKeyRing to byte[] * - * @param keysBytes + * @param keyRing * @return */ public static byte[] PGPSecretKeyRingToBytes(PGPSecretKeyRing keyRing) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index fb97f3a5c..252be1036 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -18,8 +18,8 @@ package org.sufficientlysecure.keychain.pgp; import android.content.Context; -import android.os.Bundle; +import org.openintents.openpgp.OpenPgpSignatureResult; import org.spongycastle.bcpg.ArmoredInputStream; import org.spongycastle.bcpg.SignatureSubpacketTags; import org.spongycastle.openpgp.PGPCompressedData; @@ -36,6 +36,7 @@ import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureList; import org.spongycastle.openpgp.PGPSignatureSubpacketVector; @@ -53,7 +54,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; @@ -75,9 +76,10 @@ public class PgpDecryptVerify { private InputData data; private OutputStream outStream; - private ProgressDialogUpdater progress; - boolean assumeSymmetric; - String passphrase; + private ProgressDialogUpdater progressDialogUpdater; + private boolean assumeSymmetric; + private String passphrase; + private long enforcedKeyId; private PgpDecryptVerify(Builder builder) { // private Constructor can only be called from Builder @@ -85,9 +87,10 @@ public class PgpDecryptVerify { this.data = builder.data; this.outStream = builder.outStream; - this.progress = builder.progress; + this.progressDialogUpdater = builder.progressDialogUpdater; this.assumeSymmetric = builder.assumeSymmetric; this.passphrase = builder.passphrase; + this.enforcedKeyId = builder.enforcedKeyId; } public static class Builder { @@ -97,9 +100,10 @@ public class PgpDecryptVerify { private OutputStream outStream; // optional - private ProgressDialogUpdater progress = null; + private ProgressDialogUpdater progressDialogUpdater = null; private boolean assumeSymmetric = false; private String passphrase = ""; + private long enforcedKeyId = 0; public Builder(Context context, InputData data, OutputStream outStream) { this.context = context; @@ -107,8 +111,8 @@ public class PgpDecryptVerify { this.outStream = outStream; } - public Builder progress(ProgressDialogUpdater progress) { - this.progress = progress; + public Builder progressDialogUpdater(ProgressDialogUpdater progressDialogUpdater) { + this.progressDialogUpdater = progressDialogUpdater; return this; } @@ -122,20 +126,32 @@ public class PgpDecryptVerify { return this; } + /** + * Allow this key id alone for decryption. + * This means only ciphertexts encrypted for this private key can be decrypted. + * + * @param enforcedKeyId + * @return + */ + public Builder enforcedKeyId(long enforcedKeyId) { + this.enforcedKeyId = enforcedKeyId; + return this; + } + public PgpDecryptVerify build() { return new PgpDecryptVerify(this); } } public void updateProgress(int message, int current, int total) { - if (progress != null) { - progress.setProgress(message, current, total); + if (progressDialogUpdater != null) { + progressDialogUpdater.setProgress(message, current, total); } } public void updateProgress(int current, int total) { - if (progress != null) { - progress.setProgress(current, total); + if (progressDialogUpdater != null) { + progressDialogUpdater.setProgress(current, total); } } @@ -177,9 +193,8 @@ public class PgpDecryptVerify { * @throws PGPException * @throws SignatureException */ - public Bundle execute() + public PgpDecryptVerifyResult execute() throws IOException, PgpGeneralException, PGPException, SignatureException { - // automatically works with ascii armor input and binary InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); if (in instanceof ArmoredInputStream) { @@ -207,9 +222,9 @@ public class PgpDecryptVerify { * @throws PGPException * @throws SignatureException */ - private Bundle decryptVerify(InputStream in) + private PgpDecryptVerifyResult decryptVerify(InputStream in) throws IOException, PgpGeneralException, PGPException, SignatureException { - Bundle returnData = new Bundle(); + PgpDecryptVerifyResult returnData = new PgpDecryptVerifyResult(); PGPObjectFactory pgpF = new PGPObjectFactory(in); PGPEncryptedDataList enc; @@ -277,9 +292,40 @@ public class PgpDecryptVerify { PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, encData.getKeyID()); if (secretKey != null) { + // secret key exists in database + + // allow only a specific key for decryption? + if (enforcedKeyId != 0) { + // TODO: improve this code! get master key directly! + PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, encData.getKeyID()); + long masterKeyId = PgpKeyHelper.getMasterKey(secretKeyRing).getKeyID(); + Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID()); + Log.d(Constants.TAG, "enforcedKeyId: " + enforcedKeyId); + Log.d(Constants.TAG, "masterKeyId: " + masterKeyId); + + if (enforcedKeyId != masterKeyId) { + throw new PgpGeneralException(context.getString(R.string.error_no_secret_key_found)); + } + } + pbe = encData; + + // if no passphrase was explicitly set try to get it from the cache service + if (passphrase == null) { + // returns "" if key has no passphrase + passphrase = PassphraseCacheService.getCachedPassphrase(context, encData.getKeyID()); + + // if passphrase was not cached, return here indicating that a passphrase is missing! + if (passphrase == null) { + returnData.setKeyPassphraseNeeded(true); + return returnData; + } + } + break; } + + } } @@ -289,7 +335,7 @@ public class PgpDecryptVerify { currentProgress += 5; updateProgress(R.string.progress_extracting_key, currentProgress, 100); - PGPPrivateKey privateKey = null; + PGPPrivateKey privateKey; try { PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( @@ -317,6 +363,7 @@ public class PgpDecryptVerify { PGPObjectFactory plainFact = new PGPObjectFactory(clear); Object dataChunk = plainFact.nextObject(); PGPOnePassSignature signature = null; + OpenPgpSignatureResult signatureResult = null; PGPPublicKey signatureKey = null; int signatureIndex = -1; @@ -334,7 +381,7 @@ public class PgpDecryptVerify { if (dataChunk instanceof PGPOnePassSignatureList) { updateProgress(R.string.progress_processing_signature, currentProgress, 100); - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); + signatureResult = new OpenPgpSignatureResult(); PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; for (int i = 0; i < sigList.size(); ++i) { signature = sigList.get(i); @@ -354,12 +401,12 @@ public class PgpDecryptVerify { if (signKeyRing != null) { userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); } - returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); + signatureResult.setUserId(userId); break; } } - returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); + signatureResult.setKeyId(signatureKeyId); if (signature != null) { JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() @@ -367,7 +414,7 @@ public class PgpDecryptVerify { signature.init(contentVerifierBuilderProvider, signatureKey); } else { - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); + signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY); } dataChunk = plainFact.nextObject(); @@ -405,8 +452,7 @@ public class PgpDecryptVerify { try { signature.update(buffer, 0, n); } catch (SignatureException e) { - returnData - .putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false); + signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_ERROR); signature = null; } } @@ -430,17 +476,20 @@ public class PgpDecryptVerify { PGPSignature messageSignature = signatureList.get(signatureIndex); // these are not cleartext signatures! - returnData.putBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, false); + // TODO: what about binary signatures? + signatureResult.setSignatureOnly(false); //Now check binding signatures - boolean keyBinding_isok = verifyKeyBinding(context, messageSignature, signatureKey); - boolean sig_isok = signature.verify(messageSignature); + boolean validKeyBinding = verifyKeyBinding(context, messageSignature, signatureKey); + boolean validSignature = signature.verify(messageSignature); - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, keyBinding_isok & sig_isok); + // TODO: implement CERTIFIED! + if (validKeyBinding & validSignature) { + signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED); + } } } - // TODO: test if this integrity really check works! if (encryptedData.isIntegrityProtected()) { updateProgress(R.string.progress_verifying_integrity, 95, 100); @@ -455,9 +504,12 @@ public class PgpDecryptVerify { } else { // no integrity check Log.e(Constants.TAG, "Encrypted data was not integrity protected!"); + // TODO: inform user? } updateProgress(R.string.progress_done, 100, 100); + + returnData.setSignatureResult(signatureResult); return returnData; } @@ -474,11 +526,12 @@ public class PgpDecryptVerify { * @throws PGPException * @throws SignatureException */ - private Bundle verifyCleartextSignature(ArmoredInputStream aIn) + private PgpDecryptVerifyResult verifyCleartextSignature(ArmoredInputStream aIn) throws IOException, PgpGeneralException, PGPException, SignatureException { - Bundle returnData = new Bundle(); + PgpDecryptVerifyResult returnData = new PgpDecryptVerifyResult(); + OpenPgpSignatureResult signatureResult = new OpenPgpSignatureResult(); // cleartext signatures are never encrypted ;) - returnData.putBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, true); + signatureResult.setSignatureOnly(true); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -504,8 +557,6 @@ public class PgpDecryptVerify { byte[] clearText = out.toByteArray(); outStream.write(clearText); - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); - updateProgress(R.string.progress_processing_signature, 60, 100); PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); @@ -533,15 +584,17 @@ public class PgpDecryptVerify { if (signKeyRing != null) { userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); } - returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); + signatureResult.setUserId(userId); break; } } - returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); + signatureResult.setKeyId(signatureKeyId); if (signature == null) { - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); + signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY); + returnData.setSignatureResult(signatureResult); + updateProgress(R.string.progress_done, 100, 100); return returnData; } @@ -569,12 +622,17 @@ public class PgpDecryptVerify { } while (lookAhead != -1); } - boolean sig_isok = signature.verify(); - //Now check binding signatures - boolean keyBinding_isok = verifyKeyBinding(context, signature, signatureKey); + boolean validKeyBinding = verifyKeyBinding(context, signature, signatureKey); + boolean validSignature = signature.verify(); - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, sig_isok & keyBinding_isok); + if (validSignature & validKeyBinding) { + signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED); + } + + // TODO: what about SIGNATURE_SUCCESS_CERTIFIED and SIGNATURE_ERROR???? + + returnData.setSignatureResult(signatureResult); updateProgress(R.string.progress_done, 100, 100); return returnData; @@ -582,34 +640,34 @@ public class PgpDecryptVerify { private static boolean verifyKeyBinding(Context context, PGPSignature signature, PGPPublicKey signatureKey) { long signatureKeyId = signature.getKeyID(); - boolean keyBinding_isok = false; - String userId = null; + boolean validKeyBinding = false; + PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context, signatureKeyId); PGPPublicKey mKey = null; if (signKeyRing != null) { mKey = PgpKeyHelper.getMasterKey(signKeyRing); } + if (signature.getKeyID() != mKey.getKeyID()) { - keyBinding_isok = verifyKeyBinding(mKey, signatureKey); + validKeyBinding = verifyKeyBinding(mKey, signatureKey); } else { //if the key used to make the signature was the master key, no need to check binding sigs - keyBinding_isok = true; + validKeyBinding = true; } - return keyBinding_isok; + return validKeyBinding; } private static boolean verifyKeyBinding(PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { - boolean subkeyBinding_isok = false; - boolean tmp_subkeyBinding_isok = false; - boolean primkeyBinding_isok = false; - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + boolean validSubkeyBinding = false; + boolean validTempSubkeyBinding = false; + boolean validPrimaryKeyBinding = false; + + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = + new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); Iterator<PGPSignature> itr = signingPublicKey.getSignatures(); - subkeyBinding_isok = false; - tmp_subkeyBinding_isok = false; - primkeyBinding_isok = false; while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong? //gpg has an invalid subkey binding error on key import I think, but doesn't shout //about keys without subkey signing. Can't get it to import a slightly broken one @@ -619,32 +677,36 @@ public class PgpDecryptVerify { //check and if ok, check primary key binding. try { sig.init(contentVerifierBuilderProvider, masterPublicKey); - tmp_subkeyBinding_isok = sig.verifyCertification(masterPublicKey, signingPublicKey); + validTempSubkeyBinding = sig.verifyCertification(masterPublicKey, signingPublicKey); } catch (PGPException e) { continue; } catch (SignatureException e) { continue; } - if (tmp_subkeyBinding_isok) - subkeyBinding_isok = true; - if (tmp_subkeyBinding_isok) { - primkeyBinding_isok = verifyPrimaryBinding(sig.getUnhashedSubPackets(), masterPublicKey, signingPublicKey); - if (primkeyBinding_isok) + if (validTempSubkeyBinding) + validSubkeyBinding = true; + if (validTempSubkeyBinding) { + validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getUnhashedSubPackets(), + masterPublicKey, signingPublicKey); + if (validPrimaryKeyBinding) break; - primkeyBinding_isok = verifyPrimaryBinding(sig.getHashedSubPackets(), masterPublicKey, signingPublicKey); - if (primkeyBinding_isok) + validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getHashedSubPackets(), + masterPublicKey, signingPublicKey); + if (validPrimaryKeyBinding) break; } } } - return (subkeyBinding_isok & primkeyBinding_isok); + return (validSubkeyBinding & validPrimaryKeyBinding); } - private static boolean verifyPrimaryBinding(PGPSignatureSubpacketVector Pkts, PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { - boolean primkeyBinding_isok = false; - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + private static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector Pkts, + PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { + boolean validPrimaryKeyBinding = false; + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = + new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureList eSigList; if (Pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) { @@ -660,8 +722,8 @@ public class PgpDecryptVerify { if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) { try { emSig.init(contentVerifierBuilderProvider, signingPublicKey); - primkeyBinding_isok = emSig.verifyCertification(masterPublicKey, signingPublicKey); - if (primkeyBinding_isok) + validPrimaryKeyBinding = emSig.verifyCertification(masterPublicKey, signingPublicKey); + if (validPrimaryKeyBinding) break; } catch (PGPException e) { continue; @@ -671,7 +733,8 @@ public class PgpDecryptVerify { } } } - return primkeyBinding_isok; + + return validPrimaryKeyBinding; } /** @@ -680,10 +743,9 @@ public class PgpDecryptVerify { * @param sig * @param line * @throws SignatureException - * @throws IOException */ private static void processLine(PGPSignature sig, byte[] line) - throws SignatureException, IOException { + throws SignatureException { int length = getLengthWithoutWhiteSpace(line); if (length > 0) { sig.update(line, 0, length); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java new file mode 100644 index 000000000..0477c4fdf --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.pgp; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.openintents.openpgp.OpenPgpSignatureResult; + +public class PgpDecryptVerifyResult implements Parcelable { + boolean symmetricPassphraseNeeded; + boolean keyPassphraseNeeded; + OpenPgpSignatureResult signatureResult; + + public boolean isSymmetricPassphraseNeeded() { + return symmetricPassphraseNeeded; + } + + public void setSymmetricPassphraseNeeded(boolean symmetricPassphraseNeeded) { + this.symmetricPassphraseNeeded = symmetricPassphraseNeeded; + } + + public boolean isKeyPassphraseNeeded() { + return keyPassphraseNeeded; + } + + public void setKeyPassphraseNeeded(boolean keyPassphraseNeeded) { + this.keyPassphraseNeeded = keyPassphraseNeeded; + } + + public OpenPgpSignatureResult getSignatureResult() { + return signatureResult; + } + + public void setSignatureResult(OpenPgpSignatureResult signatureResult) { + this.signatureResult = signatureResult; + } + + public PgpDecryptVerifyResult() { + + } + + public PgpDecryptVerifyResult(PgpDecryptVerifyResult b) { + this.symmetricPassphraseNeeded = b.symmetricPassphraseNeeded; + this.keyPassphraseNeeded = b.keyPassphraseNeeded; + this.signatureResult = b.signatureResult; + } + + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (symmetricPassphraseNeeded ? 1 : 0)); + dest.writeByte((byte) (keyPassphraseNeeded ? 1 : 0)); + dest.writeParcelable(signatureResult, 0); + } + + public static final Creator<PgpDecryptVerifyResult> CREATOR = new Creator<PgpDecryptVerifyResult>() { + public PgpDecryptVerifyResult createFromParcel(final Parcel source) { + PgpDecryptVerifyResult vr = new PgpDecryptVerifyResult(); + vr.symmetricPassphraseNeeded = source.readByte() == 1; + vr.keyPassphraseNeeded = source.readByte() == 1; + vr.signatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader()); + return vr; + } + + public PgpDecryptVerifyResult[] newArray(final int size) { + return new PgpDecryptVerifyResult[size]; + } + }; +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java index 7ac904d89..1db4f98b1 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -193,11 +193,10 @@ public class PgpHelper { * @param context * @param progress * @param file - * @throws FileNotFoundException * @throws IOException */ public static void deleteFileSecurely(Context context, ProgressDialogUpdater progress, File file) - throws FileNotFoundException, IOException { + throws IOException { long length = file.length(); SecureRandom random = new SecureRandom(); RandomAccessFile raf = new RandomAccessFile(file, "rws"); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index a7a574ee7..138e54f71 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -17,11 +17,9 @@ package org.sufficientlysecure.keychain.pgp; -import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -29,12 +27,10 @@ import java.util.List; import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPKeyRing; -import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; @@ -44,11 +40,9 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; import org.sufficientlysecure.keychain.util.HkpKeyServer; -import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.PositionAwareInputStream; import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; import android.content.Context; @@ -85,13 +79,14 @@ public class PgpImportExport { public boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ArmoredOutputStream aos = new ArmoredOutputStream(bos); + ArmoredOutputStream aos = null; try { + aos = new ArmoredOutputStream(bos); aos.write(keyring.getEncoded()); aos.close(); - String armouredKey = bos.toString("UTF-8"); - server.add(armouredKey); + String armoredKey = bos.toString("UTF-8"); + server.add(armoredKey); return true; } catch (IOException e) { @@ -101,7 +96,8 @@ public class PgpImportExport { return false; } finally { try { - bos.close(); + if (aos != null) aos.close(); + if (bos != null) bos.close(); } catch (IOException e) { } } @@ -161,59 +157,53 @@ public class PgpImportExport { return returnData; } - public Bundle exportKeyRings(ArrayList<Long> keyRingMasterKeyIds, int keyType, - OutputStream outStream) throws PgpGeneralException, FileNotFoundException, + public Bundle exportKeyRings(ArrayList<Long> keyRingRowIds, int keyType, + OutputStream outStream) throws PgpGeneralException, PGPException, IOException { Bundle returnData = new Bundle(); + int rowIdsSize = keyRingRowIds.size(); + updateProgress( mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, - keyRingMasterKeyIds.size()), 0, 100); + rowIdsSize), 0, 100); if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { throw new PgpGeneralException( mContext.getString(R.string.error_external_storage_not_ready)); } - - if (keyType == Id.type.secret_key) { - ArmoredOutputStream outSec = new ArmoredOutputStream(outStream); - outSec.setHeader("Version", PgpHelper.getFullVersion(mContext)); - - for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) { - updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100); - - PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId( - mContext, keyRingMasterKeyIds.get(i)); + // For each row id + for (int i = 0; i < rowIdsSize; ++i) { + // Create an output stream + ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream); + arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext)); + + // If the keyType is secret get the PGPSecretKeyRing + // based on the row id and encode it to the output + if (keyType == Id.type.secret_key) { + updateProgress(i * 100 / rowIdsSize / 2, 100); + PGPSecretKeyRing secretKeyRing = + ProviderHelper.getPGPSecretKeyRingByRowId(mContext, keyRingRowIds.get(i)); if (secretKeyRing != null) { - secretKeyRing.encode(outSec); - } - } - outSec.close(); - } else { - // export public keyrings... - ArmoredOutputStream outPub = new ArmoredOutputStream(outStream); - outPub.setHeader("Version", PgpHelper.getFullVersion(mContext)); - - for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) { - // double the needed time if exporting both public and secret parts - if (keyType == Id.type.secret_key) { - updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100); - } else { - updateProgress(i * 100 / keyRingMasterKeyIds.size(), 100); + secretKeyRing.encode(arOutStream); } - - PGPPublicKeyRing publicKeyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId( - mContext, keyRingMasterKeyIds.get(i)); + // Else if it's a public key get the PGPPublicKeyRing + // and encode that to the output + } else { + updateProgress(i * 100 / rowIdsSize, 100); + PGPPublicKeyRing publicKeyRing = + ProviderHelper.getPGPPublicKeyRingByRowId(mContext, keyRingRowIds.get(i)); if (publicKeyRing != null) { - publicKeyRing.encode(outPub); + publicKeyRing.encode(arOutStream); } } - outPub.close(); + + arOutStream.close(); } - returnData.putInt(KeychainIntentService.RESULT_EXPORT, keyRingMasterKeyIds.size()); + returnData.putInt(KeychainIntentService.RESULT_EXPORT, rowIdsSize); updateProgress(R.string.progress_done, 100, 100); @@ -234,7 +224,7 @@ public class PgpImportExport { for (PGPSecretKey testSecretKey : new IterableIterator<PGPSecretKey>( secretKeyRing.getSecretKeys())) { if (!testSecretKey.isMasterKey()) { - if (PgpKeyHelper.isSecretKeyPrivateEmpty(testSecretKey)) { + if (testSecretKey.isPrivateKeyEmpty()) { // this is bad, something is very wrong... save = false; status = Id.return_value.bad; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java index 38480a766..902dd8da9 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java @@ -32,6 +32,7 @@ import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureSubpacketVector; +import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.ProviderHelper; @@ -415,28 +416,27 @@ public class PgpKeyHelper { 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.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; - } + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: { + algorithmStr = "ElGamal"; + break; + } - default: { - algorithmStr = "Unknown"; - break; - } + default: { + algorithmStr = "Unknown"; + break; + } } if(keySize > 0) return algorithmStr + ", " + keySize + " bit"; @@ -444,31 +444,6 @@ public class PgpKeyHelper { return algorithmStr; } - /** - * Converts fingerprint to hex with whitespaces after 4 characters - * - * @param fp - * @return - */ - public static String convertFingerprintToHex(byte[] fp, boolean chunked) { - String fingerPrint = ""; - for (int i = 0; i < fp.length; ++i) { - if (chunked && i != 0 && i % 10 == 0) { - fingerPrint += " "; - } else if (chunked && i != 0 && i % 2 == 0) { - fingerPrint += " "; - } - String chunk = Integer.toHexString((fp[i] + 256) % 256).toUpperCase(Locale.US); - while (chunk.length() < 2) { - chunk = "0" + chunk; - } - fingerPrint += chunk; - } - - return fingerPrint; - - } - public static String getFingerPrint(Context context, long keyId) { PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId); // if it is no public key get it from your own keys... @@ -484,52 +459,68 @@ public class PgpKeyHelper { return convertFingerprintToHex(key.getFingerprint(), true); } - public static boolean isSecretKeyPrivateEmpty(PGPSecretKey secretKey) { - return secretKey.isPrivateKeyEmpty(); - } - -// public static boolean isSecretKeyPrivateEmpty(Context context, long keyId) { -// PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, keyId); -// if (secretKey == null) { -// Log.e(Constants.TAG, "Key could not be found!"); -// return false; // could be a public key, assume it is not empty -// } -// return isSecretKeyPrivateEmpty(secretKey); -// } - - public static String convertKeyIdToHex(long keyId) { - String fingerPrint = Long.toHexString(keyId & 0xffffffffL).toUpperCase(Locale.US); - while (fingerPrint.length() < 8) { - fingerPrint = "0" + fingerPrint; + /** + * Converts fingerprint to hex (optional: with whitespaces after 4 characters) + * <p/> + * Fingerprint is shown using lowercase characters. Studies have shown that humans can + * better differentiate between numbers and letters when letters are lowercase. + * + * @param fingerprint + * @param split split into 4 character chunks + * @return + */ + public static String convertFingerprintToHex(byte[] fingerprint, boolean split) { + String hexString = Hex.toHexString(fingerprint); + if (split) { + hexString = hexString.replaceAll("(.{4})(?!$)", "$1 "); } - return fingerPrint; + + return hexString; } /** - * TODO: documentation - * + * Convert key id from long to 64 bit hex string + * <p/> + * V4: "The Key ID is the low-order 64 bits of the fingerprint" + * <p/> + * see http://tools.ietf.org/html/rfc4880#section-12.2 + * * @param keyId * @return */ - public static String convertKeyToHex(long keyId) { - return convertKeyIdToHex(keyId >> 32) + convertKeyIdToHex(keyId); + public static String convertKeyIdToHex(long keyId) { + return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId); + } + + private static String convertKeyIdToHex32bit(long keyId) { + String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.US); + while (hexString.length() < 8) { + hexString = "0" + hexString; + } + return hexString; } - public static long convertHexToKeyId(String data) { - int len = data.length(); - String s2 = data.substring(len - 8); - String s1 = data.substring(0, len - 8); + /** + * Used in HkpKeyServer to convert hex encoded key ids back to long. + * + * @param hexString + * @return + */ + public static long convertHexToKeyId(String hexString) { + int len = hexString.length(); + String s2 = hexString.substring(len - 8); + String s1 = hexString.substring(0, len - 8); return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16); } /** * Splits userId string into naming part, email part, and comment part - * + * * @param userId * @return array with naming (0), email (1), comment (2) */ public static String[] splitUserId(String userId) { - String[] result = new String[] { null, null, null }; + String[] result = new String[]{null, null, null}; if (userId == null || userId.equals("")) { return result; @@ -550,7 +541,6 @@ public class PgpKeyHelper { result[0] = matcher.group(1); result[1] = matcher.group(3); result[2] = matcher.group(2); - return result; } return result; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 7caee4048..5ebb53f20 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -107,7 +107,7 @@ public class PgpKeyOperation { * * @param algorithmChoice * @param keySize - * @param passPhrase + * @param passphrase * @param isMasterKey * @return * @throws NoSuchAlgorithmException @@ -118,7 +118,7 @@ public class PgpKeyOperation { */ // TODO: key flags? - public PGPSecretKey createKey(int algorithmChoice, int keySize, String passPhrase, + public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase, boolean isMasterKey) throws NoSuchAlgorithmException, PGPException, NoSuchProviderException, PgpGeneralException, InvalidAlgorithmParameterException { @@ -126,8 +126,8 @@ public class PgpKeyOperation { throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit)); } - if (passPhrase == null) { - passPhrase = ""; + if (passphrase == null) { + passphrase = ""; } int algorithm = 0; @@ -181,7 +181,7 @@ public class PgpKeyOperation { // Build key encrypter and decrypter based on passphrase PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( PGPEncryptedData.CAST5, sha1Calc) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passPhrase.toCharArray()); + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); PGPSecretKey secKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), sha1Calc, isMasterKey, keyEncryptor); @@ -190,7 +190,7 @@ public class PgpKeyOperation { } public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase, - String newPassPhrase) throws IOException, PGPException, PGPException, + String newPassPhrase) throws IOException, PGPException, NoSuchProviderException { updateProgress(R.string.progress_building_key, 0, 100); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 171aa9912..f072e13be 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -342,7 +342,7 @@ public class KeychainProvider extends ContentProvider { /** * Returns type of the query (secret/public) * - * @param uri + * @param match * @return */ private int getKeyType(int match) { @@ -1039,7 +1039,8 @@ public class KeychainProvider extends ContentProvider { * Build default selection statement for KeyRings. If no extra selection is specified only build * where clause with rowId * - * @param uri + * @param defaultSelection + * @param keyType * @param selection * @return */ diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index c1b2ef6e3..f740e1c7c 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -100,7 +100,7 @@ public class ProviderHelper { } /** - * Retrieves the actual PGPPublicKeyRing object from the database blob based on the maserKeyId + * Retrieves the actual PGPPublicKeyRing object from the database blob based on the masterKeyId */ public static PGPPublicKeyRing getPGPPublicKeyRingByMasterKeyId(Context context, long masterKeyId) { @@ -123,11 +123,8 @@ public class ProviderHelper { */ public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long keyId) { PGPPublicKeyRing keyRing = getPGPPublicKeyRingByKeyId(context, keyId); - if (keyRing == null) { - return null; - } - return keyRing.getPublicKey(keyId); + return (keyRing == null)? null : keyRing.getPublicKey(keyId); } /** @@ -162,11 +159,8 @@ public class ProviderHelper { */ public static PGPSecretKey getPGPSecretKeyByKeyId(Context context, long keyId) { PGPSecretKeyRing keyRing = getPGPSecretKeyRingByKeyId(context, keyId); - if (keyRing == null) { - return null; - } - return keyRing.getSecretKey(keyId); + return (keyRing == null) ? null : keyRing.getSecretKey(keyId); } /** @@ -411,10 +405,10 @@ public class ProviderHelper { long keyRingRowId, PGPSecretKey key, int rank) throws IOException { ContentValues values = new ContentValues(); - boolean has_private = true; + boolean hasPrivate = true; if (key.isMasterKey()) { - if (PgpKeyHelper.isSecretKeyPrivateEmpty(key)) { - has_private = false; + if (key.isPrivateKeyEmpty()) { + hasPrivate = false; } } @@ -422,8 +416,8 @@ public class ProviderHelper { 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_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && has_private)); - values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && has_private)); + values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && hasPrivate)); + values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && hasPrivate)); values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key)); values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked()); values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000); @@ -481,6 +475,30 @@ public class ProviderHelper { } /** + * Private helper method + */ + private static ArrayList<Long> getKeyRingsRowIds(Context context, Uri queryUri) { + Cursor cursor = context.getContentResolver().query(queryUri, + new String[]{KeyRings._ID}, null, null, null); + + ArrayList<Long> rowIds = new ArrayList<Long>(); + if (cursor != null) { + int IdCol = cursor.getColumnIndex(KeyRings._ID); + if (cursor.moveToFirst()) { + do { + rowIds.add(cursor.getLong(IdCol)); + } while (cursor.moveToNext()); + } + } + + if (cursor != null) { + cursor.close(); + } + + return rowIds; + } + + /** * Retrieves ids of all SecretKeyRings */ public static ArrayList<Long> getSecretKeyRingsMasterKeyIds(Context context) { @@ -496,6 +514,22 @@ public class ProviderHelper { return getKeyRingsMasterKeyIds(context, queryUri); } + /** + * Retrieves ids of all SecretKeyRings + */ + public static ArrayList<Long> getSecretKeyRingsRowIds(Context context) { + Uri queryUri = KeyRings.buildSecretKeyRingsUri(); + return getKeyRingsRowIds(context, queryUri); + } + + /** + * Retrieves ids of all PublicKeyRings + */ + public static ArrayList<Long> getPublicKeyRingsRowIds(Context context) { + Uri queryUri = KeyRings.buildPublicKeyRingsUri(); + return getKeyRingsRowIds(context, queryUri); + } + public static void deletePublicKeyRing(Context context, long rowId) { ContentResolver cr = context.getContentResolver(); cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null); @@ -722,7 +756,7 @@ public class ProviderHelper { String armoredKey = bos.toString("UTF-8"); - Log.d(Constants.TAG, "armouredKey:" + armoredKey); + Log.d(Constants.TAG, "armoredKey:" + armoredKey); output.add(armoredKey); } catch (IOException e) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 302dbea0b..93238349d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -44,11 +44,13 @@ import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpImportExport; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; @@ -152,6 +154,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial public static final String EXPORT_KEY_TYPE = "export_key_type"; public static final String EXPORT_ALL = "export_all"; public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id"; + public static final String EXPORT_KEY_RING_ROW_ID = "export_key_rind_row_id"; // upload key public static final String UPLOAD_KEY_SERVER = "upload_key_server"; @@ -181,13 +184,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial // decrypt/verify public static final String RESULT_DECRYPTED_STRING = "decrypted_message"; public static final String RESULT_DECRYPTED_BYTES = "decrypted_data"; - public static final String RESULT_SIGNATURE = "signature"; - public static final String RESULT_SIGNATURE_KEY_ID = "signature_key_id"; - public static final String RESULT_SIGNATURE_USER_ID = "signature_user_id"; - public static final String RESULT_CLEARTEXT_SIGNATURE_ONLY = "signature_only"; - - public static final String RESULT_SIGNATURE_SUCCESS = "signature_success"; - public static final String RESULT_SIGNATURE_UNKNOWN = "signature_unknown"; + public static final String RESULT_DECRYPT_VERIFY_RESULT = "signature"; // import public static final String RESULT_IMPORT_ADDED = "added"; @@ -206,7 +203,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial private boolean mIsCanceled; public KeychainIntentService() { - super("ApgService"); + super("KeychainIntentService"); } @Override @@ -489,15 +486,17 @@ public class KeychainIntentService extends IntentService implements ProgressDial // verifyText and decrypt returning additional resultData values for the // verification of signatures PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream); - builder.progress(this); + builder.progressDialogUpdater(this); builder.assumeSymmetric(assumeSymmetricEncryption) .passphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); - resultData = builder.build().execute(); + PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute(); outStream.close(); + resultData.putParcelable(RESULT_DECRYPT_VERIFY_RESULT, decryptVerifyResult); + /* Output */ switch (target) { @@ -599,13 +598,23 @@ public class KeychainIntentService extends IntentService implements ProgressDial String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE); /* Operation */ + int keysTotal = 2; + int keysCreated = 0; + setProgress( + getApplicationContext().getResources().getQuantityString(R.plurals.progress_generating, keysTotal), + keysCreated, + keysTotal); PgpKeyOperation keyOperations = new PgpKeyOperation(this, this); PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa, 4096, passphrase, true); + keysCreated++; + setProgress(keysCreated, keysTotal); PGPSecretKey subKey = keyOperations.createKey(Id.choice.algorithm.rsa, 4096, passphrase, false); + keysCreated++; + setProgress(keysCreated, keysTotal); // TODO: default to one master for cert, one sub for encrypt and one sub // for sign @@ -668,10 +677,12 @@ public class KeychainIntentService extends IntentService implements ProgressDial String outputFile = data.getString(EXPORT_FILENAME); + long[] rowIds = new long[0]; + + // If not exporting all keys get the rowIds of the keys to export from the intent boolean exportAll = data.getBoolean(EXPORT_ALL); - long keyRingMasterKeyId = -1; if (!exportAll) { - keyRingMasterKeyId = data.getLong(EXPORT_KEY_RING_MASTER_KEY_ID); + rowIds = data.getLongArray(EXPORT_KEY_RING_ROW_ID); } /* Operation */ @@ -684,24 +695,26 @@ public class KeychainIntentService extends IntentService implements ProgressDial // OutputStream FileOutputStream outStream = new FileOutputStream(outputFile); - ArrayList<Long> keyRingMasterKeyIds = new ArrayList<Long>(); + ArrayList<Long> keyRingRowIds = new ArrayList<Long>(); if (exportAll) { - // get all key ring row ids based on export type + // get all key ring row ids based on export type if (keyType == Id.type.public_key) { - keyRingMasterKeyIds = ProviderHelper.getPublicKeyRingsMasterKeyIds(this); + keyRingRowIds = ProviderHelper.getPublicKeyRingsRowIds(this); } else { - keyRingMasterKeyIds = ProviderHelper.getSecretKeyRingsMasterKeyIds(this); + keyRingRowIds = ProviderHelper.getSecretKeyRingsRowIds(this); } } else { - keyRingMasterKeyIds.add(keyRingMasterKeyId); + for(long rowId : rowIds) { + keyRingRowIds.add(rowId); + } } - Bundle resultData = new Bundle(); + Bundle resultData; PgpImportExport pgpImportExport = new PgpImportExport(this, this); resultData = pgpImportExport - .exportKeyRings(keyRingMasterKeyIds, keyType, outStream); + .exportKeyRings(keyRingRowIds, keyType, outStream); sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); } catch (Exception e) { @@ -751,7 +764,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial */ // 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 + // armor blocks BufferedInputStream bufferedInput = new BufferedInputStream(new ByteArrayInputStream(downloadedKey)); try { @@ -867,10 +880,10 @@ public class KeychainIntentService extends IntentService implements ProgressDial } /** - * Set progress of ProgressDialog by sending message to handler on UI thread + * Set progressDialogUpdater of ProgressDialog by sending message to handler on UI thread */ public void setProgress(String message, int progress, int max) { - Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max=" + Log.d(Constants.TAG, "Send message by setProgress with progressDialogUpdater=" + progress + ", max=" + max); Bundle data = new Bundle(); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java index 6eba9cc83..ebc002ceb 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java @@ -21,7 +21,6 @@ import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.R; import android.app.Activity; -import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.os.Bundle; import android.os.Handler; @@ -51,21 +50,26 @@ public class KeychainIntentServiceHandler extends Handler { this.mActivity = activity; } - public KeychainIntentServiceHandler(Activity activity, ProgressDialogFragment progressDialogFragment) { + public KeychainIntentServiceHandler(Activity activity, + ProgressDialogFragment progressDialogFragment) { this.mActivity = activity; this.mProgressDialogFragment = progressDialogFragment; } - public KeychainIntentServiceHandler(Activity activity, int progressDialogMessageId, int progressDialogStyle) { - this(activity, progressDialogMessageId, progressDialogStyle, false, null); + public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage, + int progressDialogStyle) { + this(activity, progressDialogMessage, progressDialogStyle, false, null); } - public KeychainIntentServiceHandler(Activity activity, int progressDialogMessageId, + public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage, int progressDialogStyle, boolean cancelable, OnCancelListener onCancelListener) { this.mActivity = activity; - this.mProgressDialogFragment = ProgressDialogFragment.newInstance(progressDialogMessageId, - progressDialogStyle, cancelable, onCancelListener); + this.mProgressDialogFragment = ProgressDialogFragment.newInstance( + progressDialogMessage, + progressDialogStyle, + cancelable, + onCancelListener); } public void showProgressDialog(FragmentActivity activity) { @@ -84,43 +88,43 @@ public class KeychainIntentServiceHandler extends Handler { Bundle data = message.getData(); switch (message.arg1) { - case MESSAGE_OKAY: - mProgressDialogFragment.dismissAllowingStateLoss(); + case MESSAGE_OKAY: + mProgressDialogFragment.dismissAllowingStateLoss(); - break; + break; - case MESSAGE_EXCEPTION: - mProgressDialogFragment.dismissAllowingStateLoss(); + case MESSAGE_EXCEPTION: + mProgressDialogFragment.dismissAllowingStateLoss(); - // show error from service - if (data.containsKey(DATA_ERROR)) { - Toast.makeText(mActivity, - mActivity.getString(R.string.error_message, data.getString(DATA_ERROR)), - Toast.LENGTH_SHORT).show(); - } + // show error from service + if (data.containsKey(DATA_ERROR)) { + Toast.makeText(mActivity, + mActivity.getString(R.string.error_message, data.getString(DATA_ERROR)), + Toast.LENGTH_SHORT).show(); + } - break; - - case MESSAGE_UPDATE_PROGRESS: - if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) { - - // update progress from service - if (data.containsKey(DATA_MESSAGE)) { - mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE), - data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX)); - } else if (data.containsKey(DATA_MESSAGE_ID)) { - mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID), - data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX)); - } else { - mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS), - data.getInt(DATA_PROGRESS_MAX)); + break; + + case MESSAGE_UPDATE_PROGRESS: + if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) { + + // update progress from service + if (data.containsKey(DATA_MESSAGE)) { + mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE), + data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX)); + } else if (data.containsKey(DATA_MESSAGE_ID)) { + mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID), + data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX)); + } else { + mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS), + data.getInt(DATA_PROGRESS_MAX)); + } } - } - break; + break; - default: - break; + default: + break; } } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index abd2320e3..ce34d451d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -21,6 +21,7 @@ import java.util.Date; import java.util.HashMap; import java.util.Iterator; +import android.util.LongSparseArray; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPSecretKey; @@ -77,7 +78,7 @@ public class PassphraseCacheService extends Service { private BroadcastReceiver mIntentReceiver; - private HashMap<Long, String> mPassphraseCache = new HashMap<Long, String>(); + private LongSparseArray<String> mPassphraseCache = new LongSparseArray<String>(); Context mContext; @@ -347,7 +348,7 @@ public class PassphraseCacheService extends Service { Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!"); // stop whole service if no cached passphrases remaining - if (mPassphraseCache.isEmpty()) { + if (mPassphraseCache.size() == 0) { Log.d(TAG, "No passphrases remaining in memory, stopping service!"); stopSelf(); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsActivity.java index a7afc9698..178b2fc67 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsActivity.java @@ -41,7 +41,7 @@ public class AppSettingsActivity extends ActionBarActivity { super.onCreate(savedInstanceState); // Inflate a "Done" custom action bar - ActionBarHelper.setDoneView(getSupportActionBar(), R.string.api_settings_save, + ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.api_settings_save, R.drawable.ic_action_done, new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java index 8c8e6f00a..f697faa6e 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java @@ -21,7 +21,6 @@ import android.app.PendingIntent; import android.content.Intent; import android.database.Cursor; import android.net.Uri; -import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -33,9 +32,11 @@ import org.spongycastle.util.Arrays; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -103,9 +104,8 @@ public class OpenPgpService extends RemoteService { // return PendingIntent to be executed by client Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); result.putExtra(OpenPgpApi.RESULT_INTENT, pi); - + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); return result; } @@ -114,8 +114,8 @@ public class OpenPgpService extends RemoteService { } Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); result.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIdsArray); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); return result; } @@ -130,9 +130,8 @@ public class OpenPgpService extends RemoteService { // return PendingIntent to be executed by client Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); result.putExtra(OpenPgpApi.RESULT_INTENT, pi); - + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); return result; } @@ -179,9 +178,9 @@ public class OpenPgpService extends RemoteService { return result; } catch (Exception e) { Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); - result.putExtra(OpenPgpApi.RESULT_ERRORS, + result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; } } @@ -208,9 +207,9 @@ public class OpenPgpService extends RemoteService { } } else { Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); - result.putExtra(OpenPgpApi.RESULT_ERRORS, + result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, "Missing parameter user_ids or key_ids!")); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; } @@ -267,9 +266,9 @@ public class OpenPgpService extends RemoteService { return result; } catch (Exception e) { Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); - result.putExtra(OpenPgpApi.RESULT_ERRORS, + result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; } } @@ -284,98 +283,30 @@ public class OpenPgpService extends RemoteService { Intent result = new Intent(); try { - // TODO: - // fix the mess: http://stackoverflow.com/questions/148130/how-do-i-peek-at-the-first-two-bytes-in-an-inputstream - // should we allow to decrypt everything under every key id or only the one set? - // TODO: instead of trying to get the passphrase before - // pause stream when passphrase is missing and then resume - - // TODO: put this code into PgpDecryptVerify class - - // TODO: This allows to decrypt messages with ALL secret keys, not only the one for the - // app, Fix this? -// String passphrase = null; -// if (!signedOnly) { -// // BEGIN Get key -// // TODO: this input stream is consumed after PgpMain.getDecryptionKeyId()... do it -// // better! -// InputStream inputStream2 = new ByteArrayInputStream(inputBytes); -// -// // TODO: duplicates functions from DecryptActivity! -// long secretKeyId; -// try { -// if (inputStream2.markSupported()) { -// // should probably set this to the max size of two -// // pgpF objects, if it even needs to be anything other -// // than 0. -// inputStream2.mark(200); -// } -// secretKeyId = PgpHelper.getDecryptionKeyId(this, inputStream2); -// if (secretKeyId == Id.key.none) { -// throw new PgpGeneralException(getString(R.string.error_no_secret_key_found)); -// } -// } catch (NoAsymmetricEncryptionException e) { -// if (inputStream2.markSupported()) { -// inputStream2.reset(); -// } -// secretKeyId = Id.key.symmetric; -// if (!PgpDecryptVerify.hasSymmetricEncryption(this, inputStream2)) { -// throw new PgpGeneralException( -// getString(R.string.error_no_known_encryption_found)); -// } -// // we do not support symmetric decryption from the API! -// throw new Exception("Symmetric decryption is not supported!"); -// } -// -// Log.d(Constants.TAG, "secretKeyId " + secretKeyId); - - // NOTE: currently this only gets the passphrase for the key set for this client - String passphrase; - if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) { - passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); - } else { - passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId()); - } - if (passphrase == null) { - // get PendingIntent for passphrase input, add it to given params and return to client - Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId()); - return passphraseBundle; - } - + String passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); long inputLength = is.available(); InputData inputData = new InputData(is, inputLength); - Bundle outputBundle; PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os); - - builder.assumeSymmetric(false) + builder.assumeSymmetric(false) // no support for symmetric encryption + .enforcedKeyId(appSettings.getKeyId()) // allow only the private key for this app for decryption .passphrase(passphrase); - // TODO: this also decrypts with other secret keys that have no passphrase!!! - outputBundle = builder.build().execute(); - - //TODO: instead of using all these wrapping use OpenPgpSignatureResult directly - // in DecryptVerify class and then in DecryptActivity - boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE, false); - if (signature) { - long signatureKeyId = outputBundle - .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, 0); - String signatureUserId = outputBundle - .getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID); - boolean signatureSuccess = outputBundle - .getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false); - boolean signatureUnknown = outputBundle - .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, false); - boolean signatureOnly = outputBundle - .getBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, false); - - int signatureStatus = OpenPgpSignatureResult.SIGNATURE_ERROR; - if (signatureSuccess) { - signatureStatus = OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED; - } else if (signatureUnknown) { - signatureStatus = OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY; - - // If signature is unknown we return an additional PendingIntent + // TODO: currently does not support binary signed-only content + PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute(); + + if (decryptVerifyResult.isKeyPassphraseNeeded()) { + // get PendingIntent for passphrase input, add it to given params and return to client + Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId()); + return passphraseBundle; + } else if (decryptVerifyResult.isSymmetricPassphraseNeeded()) { + throw new PgpGeneralException("Decryption of symmetric content not supported by API!"); + } + + OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult(); + if (signatureResult != null) { + if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY) { + // If signature is unknown we return an _additional_ PendingIntent // to retrieve the missing key // TODO!!! Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); @@ -389,11 +320,9 @@ public class OpenPgpService extends RemoteService { result.putExtra(OpenPgpApi.RESULT_INTENT, pi); } - - OpenPgpSignatureResult sigResult = new OpenPgpSignatureResult(signatureStatus, - signatureUserId, signatureOnly, signatureKeyId); - result.putExtra(OpenPgpApi.RESULT_SIGNATURE, sigResult); + result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult); } + } finally { is.close(); os.close(); @@ -403,9 +332,44 @@ public class OpenPgpService extends RemoteService { return result; } catch (Exception e) { Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_ERROR, + new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); - result.putExtra(OpenPgpApi.RESULT_ERRORS, + return result; + } + } + + private Intent getKeyImpl(Intent data) { + try { + long keyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0); + + if (ProviderHelper.getPGPPublicKeyByKeyId(this, keyId) == null) { + Intent result = new Intent(); + + // If keys are not in db we return an additional PendingIntent + // to retrieve the missing key + // TODO!!! + Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); + intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE); + intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo"); + intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); + + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), + PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0); + + result.putExtra(OpenPgpApi.RESULT_INTENT, pi); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); + return result; + } else { + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + return result; + } + } catch (Exception e) { + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; } } @@ -431,7 +395,7 @@ public class OpenPgpService extends RemoteService { if (data == null) { Intent result = new Intent(); OpenPgpError error = new OpenPgpError(OpenPgpError.GENERIC_ERROR, "params Bundle required!"); - result.putExtra(OpenPgpApi.RESULT_ERRORS, error); + result.putExtra(OpenPgpApi.RESULT_ERROR, error); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; } @@ -440,7 +404,7 @@ public class OpenPgpService extends RemoteService { if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) { Intent result = new Intent(); OpenPgpError error = new OpenPgpError(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!"); - result.putExtra(OpenPgpApi.RESULT_ERRORS, error); + result.putExtra(OpenPgpApi.RESULT_ERROR, error); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; } @@ -471,13 +435,12 @@ public class OpenPgpService extends RemoteService { return signImpl(data, input, output, appSettings); } else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) { return encryptAndSignImpl(data, input, output, appSettings, false); - } else if (OpenPgpApi.ACTION_SIGN_AND_ENCTYPT.equals(action)) { + } else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) { return encryptAndSignImpl(data, input, output, appSettings, true); } else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) { return decryptAndVerifyImpl(data, input, output, appSettings); - } else if (OpenPgpApi.ACTION_DOWNLOAD_KEYS.equals(action)) { - // TODO! - return null; + } else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) { + return getKeyImpl(data); } else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) { return getKeyIdsImpl(data); } else { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java index e7b3b2945..cb556be39 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java @@ -72,7 +72,7 @@ public abstract class RemoteService extends Service { // return error Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); - result.putExtra(OpenPgpApi.RESULT_ERRORS, + result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); return result; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java index 11b3ee217..8fb562884 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java @@ -88,7 +88,7 @@ public class RemoteServiceActivity extends ActionBarActivity { final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow, + ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.api_register_allow, R.drawable.ic_action_done, new View.OnClickListener() { @Override public void onClick(View v) { @@ -108,7 +108,7 @@ public class RemoteServiceActivity extends ActionBarActivity { RemoteServiceActivity.this.finish(); } } - }, R.string.api_register_disallow, new View.OnClickListener() { + }, R.string.api_register_disallow, R.drawable.ic_action_cancel, new View.OnClickListener() { @Override public void onClick(View v) { // Disallow @@ -161,7 +161,7 @@ public class RemoteServiceActivity extends ActionBarActivity { } // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, + ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done, new View.OnClickListener() { @Override public void onClick(View v) { @@ -173,7 +173,7 @@ public class RemoteServiceActivity extends ActionBarActivity { RemoteServiceActivity.this.setResult(RESULT_OK, resultData); RemoteServiceActivity.this.finish(); } - }, R.string.btn_do_not_save, new View.OnClickListener() { + }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() { @Override public void onClick(View v) { // cancel @@ -214,7 +214,7 @@ public class RemoteServiceActivity extends ActionBarActivity { String text = "<font color=\"red\">" + errorMessage + "</font>"; // Inflate a "Done" custom action bar view - ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_okay, + ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done, new View.OnClickListener() { @Override diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index aab6b81d9..029dda1a0 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -230,7 +230,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements // Message is received after signing is done in ApgService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, - R.string.progress_signing, ProgressDialog.STYLE_SPINNER) { + getString(R.string.progress_signing), ProgressDialog.STYLE_SPINNER) { public void handleMessage(Message message) { // handle messages by standard ApgHandler first super.handleMessage(message); @@ -249,7 +249,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements finish(); } } - }; + } }; // Create a new Messenger for the communication back @@ -283,7 +283,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements // Message is received after uploading is done in ApgService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, - R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) { + getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { // handle messages by standard ApgHandler first super.handleMessage(message); @@ -295,7 +295,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements setResult(RESULT_OK); finish(); } - }; + } }; // Create a new Messenger for the communication back diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 9bb675db0..38d763ce4 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -25,6 +25,7 @@ import java.io.FileNotFoundException; import java.io.InputStream; import java.util.regex.Matcher; +import org.openintents.openpgp.OpenPgpSignatureResult; import org.spongycastle.openpgp.PGPPublicKeyRing; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; @@ -32,6 +33,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.FileHelper; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; @@ -528,6 +530,10 @@ public class DecryptActivity extends DrawerActivity { Log.e(Constants.TAG, "File not found!", e); AppMsg.makeText(this, getString(R.string.error_file_not_found, e.getMessage()), AppMsg.STYLE_ALERT).show(); + } finally { + try { + if (inStream != null) inStream.close(); + } catch (Exception e){ } } } else { inStream = new ByteArrayInputStream(mMessage.getText().toString().getBytes()); @@ -644,7 +650,7 @@ public class DecryptActivity extends DrawerActivity { // Message is received after encrypting is done in ApgService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, - R.string.progress_decrypting, ProgressDialog.STYLE_HORIZONTAL) { + getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { // handle messages by standard ApgHandler first super.handleMessage(message); @@ -690,11 +696,15 @@ public class DecryptActivity extends DrawerActivity { } - if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE)) { - String userId = returnData - .getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID); - mSignatureKeyId = returnData - .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID); + PgpDecryptVerifyResult decryptVerifyResult = + returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT); + + OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult(); + + if (signatureResult != null) { + + String userId = signatureResult.getUserId(); + mSignatureKeyId = signatureResult.getKeyId(); mUserIdRest.setText("id: " + PgpKeyHelper.convertKeyIdToHex(mSignatureKeyId)); if (userId == null) { @@ -707,26 +717,37 @@ public class DecryptActivity extends DrawerActivity { } mUserId.setText(userId); - if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS)) { - mSignatureStatusImage.setImageResource(R.drawable.overlay_ok); - mLookupKey.setVisibility(View.GONE); - } else if (returnData - .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN)) { - mSignatureStatusImage.setImageResource(R.drawable.overlay_error); - mLookupKey.setVisibility(View.VISIBLE); - AppMsg.makeText(DecryptActivity.this, - R.string.unknown_signature, - AppMsg.STYLE_ALERT).show(); - } else { - mSignatureStatusImage.setImageResource(R.drawable.overlay_error); - mLookupKey.setVisibility(View.GONE); + switch (signatureResult.getStatus()) { + case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: { + mSignatureStatusImage.setImageResource(R.drawable.overlay_ok); + mLookupKey.setVisibility(View.GONE); + break; + } + + // TODO! +// case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: { +// break; +// } + + case OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY: { + mSignatureStatusImage.setImageResource(R.drawable.overlay_error); + mLookupKey.setVisibility(View.VISIBLE); + AppMsg.makeText(DecryptActivity.this, + R.string.unknown_signature, + AppMsg.STYLE_ALERT).show(); + break; + } + + default: { + mSignatureStatusImage.setImageResource(R.drawable.overlay_error); + mLookupKey.setVisibility(View.GONE); + break; + } } mSignatureLayout.setVisibility(View.VISIBLE); } } } - - ; }; // Create a new Messenger for the communication back diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index e2822c898..0c35bb2b1 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -92,12 +92,12 @@ public class EditKeyActivity extends ActionBarActivity { private SectionView mUserIdsView; private SectionView mKeysView; - private String mCurrentPassPhrase = null; + private String mCurrentPassphrase = null; private String mNewPassPhrase = null; private String mSavedNewPassPhrase = null; private boolean mIsPassPhraseSet; - private BootstrapButton mChangePassPhrase; + private BootstrapButton mChangePassphrase; private CheckBox mNoPassphrase; @@ -134,14 +134,14 @@ public class EditKeyActivity extends ActionBarActivity { * @param intent */ private void handleActionCreateKey(Intent intent) { - // Inflate a "Done"/"Cancel" custom action bar - ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_save, + // Inflate a "Save"/"Cancel" custom action bar + ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save, new View.OnClickListener() { @Override public void onClick(View v) { saveClicked(); } - }, R.string.btn_do_not_save, new View.OnClickListener() { + }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() { @Override public void onClick(View v) { cancelClicked(); @@ -150,7 +150,7 @@ public class EditKeyActivity extends ActionBarActivity { Bundle extras = intent.getExtras(); - mCurrentPassPhrase = ""; + mCurrentPassphrase = ""; if (extras != null) { // if userId is given, prefill the fields @@ -165,7 +165,7 @@ public class EditKeyActivity extends ActionBarActivity { if (noPassphrase) { // check "no passphrase" checkbox and remove button mNoPassphrase.setChecked(true); - mChangePassPhrase.setVisibility(View.GONE); + mChangePassphrase.setVisibility(View.GONE); } } @@ -181,13 +181,14 @@ public class EditKeyActivity extends ActionBarActivity { // fill values for this action Bundle data = new Bundle(); data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, - mCurrentPassPhrase); + mCurrentPassphrase); serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data); // Message is received after generating is done in ApgService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( - this, R.string.progress_generating, ProgressDialog.STYLE_SPINNER, true, + this, getResources().getQuantityString(R.plurals.progress_generating, 1), + ProgressDialog.STYLE_HORIZONTAL, true, new DialogInterface.OnCancelListener() { @Override @@ -224,7 +225,7 @@ public class EditKeyActivity extends ActionBarActivity { buildLayout(); } - }; + } }; // Create a new Messenger for the communication back @@ -248,8 +249,8 @@ public class EditKeyActivity extends ActionBarActivity { * @param intent */ private void handleActionEditKey(Intent intent) { - // Inflate a "Done"/"Cancel" custom action bar - ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_save, + // Inflate a "Save"/"Cancel" custom action bar + ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save, new View.OnClickListener() { @Override public void onClick(View v) { @@ -279,9 +280,9 @@ public class EditKeyActivity extends ActionBarActivity { @Override public void handleMessage(Message message) { if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - String passPhrase = PassphraseCacheService.getCachedPassphrase( + String passphrase = PassphraseCacheService.getCachedPassphrase( EditKeyActivity.this, masterKeyId); - mCurrentPassPhrase = passPhrase; + mCurrentPassphrase = passphrase; finallySaveClicked(); } } @@ -326,8 +327,8 @@ public class EditKeyActivity extends ActionBarActivity { cancelClicked(); return true; case R.id.menu_key_edit_export_file: - mExportHelper.showExportKeysDialog(mDataUri, Id.type.secret_key, Constants.path.APP_DIR - + "/secexport.asc"); + long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())}; + mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.path.APP_DIR_FILE_SEC); return true; case R.id.menu_key_edit_delete: { // Message is received after key is deleted @@ -371,14 +372,14 @@ public class EditKeyActivity extends ActionBarActivity { } } - mCurrentPassPhrase = ""; + mCurrentPassphrase = ""; buildLayout(); mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId); if (!mIsPassPhraseSet) { // check "no passphrase" checkbox and remove button mNoPassphrase.setChecked(true); - mChangePassPhrase.setVisibility(View.GONE); + mChangePassphrase.setVisibility(View.GONE); } } @@ -408,7 +409,7 @@ public class EditKeyActivity extends ActionBarActivity { // set title based on isPassphraseSet() int title = -1; if (isPassphraseSet()) { - title = R.string.title_change_pass_phrase; + title = R.string.title_change_passphrase; } else { title = R.string.title_set_passphrase; } @@ -427,7 +428,7 @@ public class EditKeyActivity extends ActionBarActivity { setContentView(R.layout.edit_key_activity); // find views - mChangePassPhrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_pass_phrase); + mChangePassphrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_passphrase); mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase); // Build layout based on given userIds and keys @@ -447,7 +448,7 @@ public class EditKeyActivity extends ActionBarActivity { updatePassPhraseButtonText(); - mChangePassPhrase.setOnClickListener(new OnClickListener() { + mChangePassphrase.setOnClickListener(new OnClickListener() { public void onClick(View v) { showSetPassphraseDialog(); } @@ -462,10 +463,10 @@ public class EditKeyActivity extends ActionBarActivity { // remove passphrase mSavedNewPassPhrase = mNewPassPhrase; mNewPassPhrase = ""; - mChangePassPhrase.setVisibility(View.GONE); + mChangePassphrase.setVisibility(View.GONE); } else { mNewPassPhrase = mSavedNewPassPhrase; - mChangePassPhrase.setVisibility(View.VISIBLE); + mChangePassphrase.setVisibility(View.VISIBLE); } } }); @@ -504,7 +505,7 @@ public class EditKeyActivity extends ActionBarActivity { if (passphrase == null) { showPassphraseDialog(masterKeyId, masterCanSign); } else { - mCurrentPassPhrase = passphrase; + mCurrentPassphrase = passphrase; finallySaveClicked(); } } catch (PgpGeneralException e) { @@ -523,7 +524,7 @@ public class EditKeyActivity extends ActionBarActivity { // fill values for this action Bundle data = new Bundle(); data.putString(KeychainIntentService.SAVE_KEYRING_CURRENT_PASSPHRASE, - mCurrentPassPhrase); + mCurrentPassphrase); data.putString(KeychainIntentService.SAVE_KEYRING_NEW_PASSPHRASE, mNewPassPhrase); data.putStringArrayList(KeychainIntentService.SAVE_KEYRING_USER_IDS, getUserIds(mUserIdsView)); @@ -541,7 +542,7 @@ public class EditKeyActivity extends ActionBarActivity { // Message is received after saving is done in ApgService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, - R.string.progress_saving, ProgressDialog.STYLE_HORIZONTAL) { + getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { // handle messages by standard ApgHandler first super.handleMessage(message); @@ -559,7 +560,7 @@ public class EditKeyActivity extends ActionBarActivity { setResult(RESULT_OK, data); finish(); } - }; + } }; // Create a new Messenger for the communication back @@ -694,7 +695,7 @@ public class EditKeyActivity extends ActionBarActivity { } private void updatePassPhraseButtonText() { - mChangePassPhrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase) + mChangePassphrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase) : getString(R.string.btn_set_passphrase)); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index 9da6c1b9f..8f8952763 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -52,17 +52,21 @@ import android.os.Message; import android.os.Messenger; import android.view.View; import android.view.View.OnClickListener; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.EditText; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import android.widget.ViewFlipper; import com.beardedhen.androidbootstrap.BootstrapButton; +import com.beardedhen.androidbootstrap.FontAwesomeText; import com.devspark.appmsg.AppMsg; public class EncryptActivity extends DrawerActivity { @@ -101,18 +105,24 @@ public class EncryptActivity extends DrawerActivity { private int mEncryptTarget; - private EditText mPassPhrase = null; - private EditText mPassPhraseAgain = null; + private EditText mPassphrase = null; + private EditText mPassphraseAgain = null; private CheckBox mAsciiArmor = null; private Spinner mFileCompression = null; private EditText mFilename = null; private CheckBox mDeleteAfter = null; + private CheckBox mShareAfter = null; private BootstrapButton mBrowse = null; private String mInputFilename = null; private String mOutputFilename = null; + private Integer mShortAnimationDuration = null; + private boolean mFileAdvancedSettingsVisible = false; + private TextView mFileAdvancedSettings = null; + private LinearLayout mFileAdvancedSettingsContainer = null; + private FontAwesomeText mAdvancedSettingsIcon; private boolean mAsciiArmorDemand = false; private boolean mOverrideAsciiArmor = false; @@ -147,6 +157,9 @@ public class EncryptActivity extends DrawerActivity { updateMode(); updateActionBarButtons(); + + // retrieve and cache the system's short animation time + mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); } /** @@ -436,14 +449,14 @@ public class EncryptActivity extends DrawerActivity { // symmetric encryption if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { boolean gotPassPhrase = false; - String passPhrase = mPassPhrase.getText().toString(); - String passPhraseAgain = mPassPhraseAgain.getText().toString(); - if (!passPhrase.equals(passPhraseAgain)) { + String passphrase = mPassphrase.getText().toString(); + String passphraseAgain = mPassphraseAgain.getText().toString(); + if (!passphrase.equals(passphraseAgain)) { AppMsg.makeText(this, R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show(); return; } - gotPassPhrase = (passPhrase.length() != 0); + gotPassPhrase = (passphrase.length() != 0); if (!gotPassPhrase) { AppMsg.makeText(this, R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT) .show(); @@ -550,11 +563,11 @@ public class EncryptActivity extends DrawerActivity { if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { Log.d(Constants.TAG, "Symmetric encryption enabled!"); - String passPhrase = mPassPhrase.getText().toString(); - if (passPhrase.length() == 0) { - passPhrase = null; + String passphrase = mPassphrase.getText().toString(); + if (passphrase.length() == 0) { + passphrase = null; } - data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passPhrase); + data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase); } else { mSecretKeyIdToPass = mSecretKeyId; encryptionKeyIds = mEncryptionKeyIds; @@ -604,7 +617,7 @@ public class EncryptActivity extends DrawerActivity { // Message is received after encrypting is done in ApgService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, - R.string.progress_encrypting, ProgressDialog.STYLE_HORIZONTAL) { + getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { // handle messages by standard ApgHandler first super.handleMessage(message); @@ -650,6 +663,15 @@ public class EncryptActivity extends DrawerActivity { .newInstance(mInputFilename); deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); } + + if (mShareAfter.isChecked()) { + // Share encrypted file + Intent sendFileIntent = new Intent(Intent.ACTION_SEND); + sendFileIntent.setType("*/*"); + sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename)); + startActivity(Intent.createChooser(sendFileIntent, + getString(R.string.title_send_file))); + } break; default: @@ -659,8 +681,6 @@ public class EncryptActivity extends DrawerActivity { } } } - - ; }; // Create a new Messenger for the communication back @@ -766,8 +786,8 @@ public class EncryptActivity extends DrawerActivity { mMainUserId = (TextView) findViewById(R.id.mainUserId); mMainUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); - mPassPhrase = (EditText) findViewById(R.id.passPhrase); - mPassPhraseAgain = (EditText) findViewById(R.id.passPhraseAgain); + mPassphrase = (EditText) findViewById(R.id.passphrase); + mPassphraseAgain = (EditText) findViewById(R.id.passphraseAgain); // measure the height of the source_file view and set the message view's min height to that, // so it fills mSource fully... bit of a hack. @@ -785,6 +805,50 @@ public class EncryptActivity extends DrawerActivity { } }); + mAdvancedSettingsIcon = (FontAwesomeText) findViewById(R.id.advancedSettingsIcon); + mFileAdvancedSettingsContainer = (LinearLayout) findViewById(R.id.fileAdvancedSettingsContainer); + mFileAdvancedSettings = (TextView) findViewById(R.id.advancedSettings); + + LinearLayout advancedSettingsControl = (LinearLayout) findViewById(R.id.advancedSettingsControl); + advancedSettingsControl.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mFileAdvancedSettingsVisible = !mFileAdvancedSettingsVisible; + if (mFileAdvancedSettingsVisible) { + mAdvancedSettingsIcon.setIcon("fa-chevron-down"); + mFileAdvancedSettingsContainer.setVisibility(View.VISIBLE); + AlphaAnimation animation = new AlphaAnimation(0f, 1f); + animation.setDuration(mShortAnimationDuration); + mFileAdvancedSettingsContainer.startAnimation(animation); + mFileAdvancedSettings.setText(R.string.btn_encryption_advanced_settings_hide); + + } else { + mAdvancedSettingsIcon.setIcon("fa-chevron-right"); + AlphaAnimation animation = new AlphaAnimation(1f, 0f); + animation.setDuration(mShortAnimationDuration); + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + // do nothing + } + + @Override + public void onAnimationEnd(Animation animation) { + // making sure that at the end the container is completely removed from view + mFileAdvancedSettingsContainer.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + // do nothing + } + }); + mFileAdvancedSettingsContainer.startAnimation(animation); + mFileAdvancedSettings.setText(R.string.btn_encryption_advanced_settings_show); + } + } + }); + mFileCompression = (Spinner) findViewById(R.id.fileCompression); Choice[] choices = new Choice[]{ new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " (" @@ -809,6 +873,7 @@ public class EncryptActivity extends DrawerActivity { } mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption); + mShareAfter = (CheckBox) findViewById(R.id.shareAfterEncryption); mAsciiArmor = (CheckBox) findViewById(R.id.asciiArmour); mAsciiArmor.setChecked(Preferences.getPreferences(this).getDefaultAsciiArmour()); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java index 9ccd7e088..ac8250bef 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java @@ -22,16 +22,11 @@ import java.util.ArrayList; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter; -import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; -import android.widget.TextView; public class HelpActivity extends ActionBarActivity { public static final String EXTRA_SELECTED_TAB = "selectedTab"; @@ -64,19 +59,19 @@ public class HelpActivity extends ActionBarActivity { Bundle startBundle = new Bundle(); startBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_start); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)), - HelpHtmlFragment.class, startBundle, (selectedTab == 0 ? true : false)); + HelpHtmlFragment.class, startBundle, (selectedTab == 0) ); Bundle nfcBundle = new Bundle(); nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_nfc_beam); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)), - HelpHtmlFragment.class, nfcBundle, (selectedTab == 1 ? true : false)); + HelpHtmlFragment.class, nfcBundle, (selectedTab == 1) ); Bundle changelogBundle = new Bundle(); changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_changelog); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)), - HelpHtmlFragment.class, changelogBundle, (selectedTab == 2 ? true : false)); + HelpHtmlFragment.class, changelogBundle, (selectedTab == 2) ); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)), - HelpAboutFragment.class, null, (selectedTab == 3 ? true : false)); + HelpAboutFragment.class, null, (selectedTab == 3) ); } }
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 5ac421a44..f04a0e227 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -161,7 +161,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa } else if (extras.containsKey(EXTRA_KEY_ID)) { long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0); if (keyId != 0) { - query = "0x" + PgpKeyHelper.convertKeyToHex(keyId); + query = PgpKeyHelper.convertKeyIdToHex(keyId); } } else if (extras.containsKey(EXTRA_FINGERPRINT)) { String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT); @@ -360,51 +360,53 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa // } - // Message is received after importing is done in ApgService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, - R.string.progress_importing, ProgressDialog.STYLE_HORIZONTAL) { - public void handleMessage(Message message) { - // handle messages by standard ApgHandler first - super.handleMessage(message); - - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - // get returned data bundle - Bundle returnData = message.getData(); - - int added = returnData.getInt(KeychainIntentService.RESULT_IMPORT_ADDED); - int updated = returnData - .getInt(KeychainIntentService.RESULT_IMPORT_UPDATED); - int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD); - String toastMessage; - if (added > 0 && updated > 0) { - String addedStr = getResources().getQuantityString( - R.plurals.keys_added_and_updated_1, added, added); - String updatedStr = getResources().getQuantityString( - R.plurals.keys_added_and_updated_2, updated, updated); - toastMessage = addedStr + updatedStr; - } else if (added > 0) { - toastMessage = getResources().getQuantityString(R.plurals.keys_added, - added, added); - } else if (updated > 0) { - toastMessage = getResources().getQuantityString(R.plurals.keys_updated, - updated, updated); - } else { - toastMessage = getString(R.string.no_keys_added_or_updated); - } - AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO) - .show(); - if (bad > 0) { - BadImportKeyDialogFragment badImportKeyDialogFragment = BadImportKeyDialogFragment.newInstance(bad); - badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog"); - } - } - } - }; - /** * Import keys with mImportData */ public void importKeys() { + // Message is received after importing is done in ApgService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( + this, + getString(R.string.progress_importing), + ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + // get returned data bundle + Bundle returnData = message.getData(); + + int added = returnData.getInt(KeychainIntentService.RESULT_IMPORT_ADDED); + int updated = returnData + .getInt(KeychainIntentService.RESULT_IMPORT_UPDATED); + int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD); + String toastMessage; + if (added > 0 && updated > 0) { + String addedStr = getResources().getQuantityString( + R.plurals.keys_added_and_updated_1, added, added); + String updatedStr = getResources().getQuantityString( + R.plurals.keys_added_and_updated_2, updated, updated); + toastMessage = addedStr + updatedStr; + } else if (added > 0) { + toastMessage = getResources().getQuantityString(R.plurals.keys_added, + added, added); + } else if (updated > 0) { + toastMessage = getResources().getQuantityString(R.plurals.keys_updated, + updated, updated); + } else { + toastMessage = getString(R.string.no_keys_added_or_updated); + } + AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO) + .show(); + if (bad > 0) { + BadImportKeyDialogFragment badImportKeyDialogFragment = BadImportKeyDialogFragment.newInstance(bad); + badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog"); + } + } + } + }; + if (mListFragment.getKeyBytes() != null || mListFragment.getDataUri() != null) { Log.d(Constants.TAG, "importKeys started"); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index 1118f0264..a6917d6f4 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -219,27 +219,44 @@ public class ImportKeysListFragment extends ListFragment implements } else { setListShownNoAnimation(true); } + + Exception error = data.getError(); + switch (loader.getId()) { case LOADER_ID_BYTES: + + if(error == null){ + // No error + } else if(error instanceof ImportKeysListLoader.FileHasNoContent) { + AppMsg.makeText(getActivity(), R.string.error_import_file_no_content, + AppMsg.STYLE_ALERT).show(); + } else if(error instanceof ImportKeysListLoader.NonPgpPart) { + AppMsg.makeText(getActivity(), + ((ImportKeysListLoader.NonPgpPart) error).getCount() + " " + getResources(). + getQuantityString(R.plurals.error_import_non_pgp_part, + ((ImportKeysListLoader.NonPgpPart) error).getCount()), + new AppMsg.Style(AppMsg.LENGTH_LONG, R.color.confirm)).show(); + } else { + AppMsg.makeText(getActivity(), R.string.error_generic_report_bug, + new AppMsg.Style(AppMsg.LENGTH_LONG, R.color.alert)).show(); + } break; case LOADER_ID_SERVER_QUERY: - Exception error = data.getError(); - - if(error == null){ + if(error == null) { AppMsg.makeText( getActivity(), getResources().getQuantityString(R.plurals.keys_found, mAdapter.getCount(), mAdapter.getCount()), AppMsg.STYLE_INFO ).show(); - } else if(error instanceof KeyServer.InsufficientQuery){ + } else if(error instanceof KeyServer.InsufficientQuery) { AppMsg.makeText(getActivity(), R.string.error_keyserver_insufficient_query, AppMsg.STYLE_ALERT).show(); - }else if(error instanceof KeyServer.QueryException){ + } else if(error instanceof KeyServer.QueryException) { AppMsg.makeText(getActivity(), R.string.error_keyserver_query, AppMsg.STYLE_ALERT).show(); - }else if(error instanceof KeyServer.TooManyResponses){ + } else if(error instanceof KeyServer.TooManyResponses) { AppMsg.makeText(getActivity(), R.string.error_keyserver_too_many_responses, AppMsg.STYLE_ALERT).show(); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index 0bbe2edb1..9eebbed64 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -59,8 +59,8 @@ public class KeyListActivity extends DrawerActivity { return true; case R.id.menu_key_list_export: - mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR - + "/pubexport.asc"); + // TODO fix this for unified keylist + mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB); return true; default: diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index ea37677d1..c4a694bba 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -19,11 +19,14 @@ package org.sufficientlysecure.keychain.ui; import java.util.HashMap; import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes; @@ -53,13 +56,21 @@ import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.app.ActionBarActivity; +import android.support.v7.widget.SearchView; +import android.text.Spannable; +import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.view.animation.AnimationUtils; import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.Button; @@ -73,24 +84,37 @@ import com.beardedhen.androidbootstrap.BootstrapButton; * Public key list with sticky list headers. It does _not_ extend ListFragment because it uses * StickyListHeaders library which does not extend upon ListView. */ -public class KeyListFragment extends Fragment implements AdapterView.OnItemClickListener, +public class KeyListFragment extends Fragment implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor> { private KeyListAdapter mAdapter; private StickyListHeadersListView mStickyList; + // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 + boolean mListShown; + View mProgressContainer; + View mListContainer; + + private String mCurQuery; + private SearchView mSearchView; // empty list layout private BootstrapButton mButtonEmptyCreate; private BootstrapButton mButtonEmptyImport; + /** * Load custom layout with StickyListView from library */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.key_list_fragment, container, false); + View root = inflater.inflate(R.layout.key_list_fragment, container, false); + + mStickyList = (StickyListHeadersListView) root.findViewById(R.id.key_list_list); + mStickyList.setOnItemClickListener(this); - mButtonEmptyCreate = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_create); + + // empty view + mButtonEmptyCreate = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_create); mButtonEmptyCreate.setOnClickListener(new OnClickListener() { @Override @@ -102,8 +126,7 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick startActivityForResult(intent, 0); } }); - - mButtonEmptyImport = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_import); + mButtonEmptyImport = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_import); mButtonEmptyImport.setOnClickListener(new OnClickListener() { @Override @@ -114,7 +137,12 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick } }); - return view; + // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 + mListContainer = root.findViewById(R.id.key_list_list_container); + mProgressContainer = root.findViewById(R.id.key_list_progress_container); + mListShown = true; + + return root; } /** @@ -125,8 +153,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list); - mStickyList.setOnItemClickListener(this); mStickyList.setAreHeadersSticky(true); mStickyList.setDrawingListUnderStickyHeader(false); @@ -137,7 +163,7 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick } // this view is made visible if no data is available - mStickyList.setEmptyView(getActivity().findViewById(R.id.empty)); + mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty)); /* * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only @@ -147,8 +173,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() { - private int count = 0; - @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { android.view.MenuInflater inflater = getActivity().getMenuInflater(); @@ -174,7 +198,7 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick break; } case R.id.menu_key_list_multi_delete: { - ids = mAdapter.getCurrentSelectedItemIds(); + ids = mStickyList.getWrappedList().getCheckedItemIds(); showDeleteKeyDialog(mode, ids); break; } @@ -184,7 +208,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick @Override public void onDestroyActionMode(ActionMode mode) { - count = 0; mAdapter.clearSelection(); } @@ -192,13 +215,11 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { if (checked) { - count++; mAdapter.setNewSelection(position, checked); } else { - count--; mAdapter.removeSelection(position); } - + int count = mStickyList.getCheckedItemCount(); String keysSelected = getResources().getQuantityString( R.plurals.key_list_selected_keys, count, count); mode.setTitle(keysSelected); @@ -207,9 +228,12 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick }); } - // NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading + // We have a menu item to show in action bar. + setHasOptionsMenu(true); + + // NOTE: Not supported by StickyListHeader, but reimplemented here // Start out with a progress indicator. - // setListShown(false); + setListShown(false); // Create an empty adapter we will use to display the loaded data. mAdapter = new KeyListAdapter(getActivity(), null, Id.type.public_key); @@ -238,27 +262,33 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); - + String where = null; + String whereArgs[] = null; + if (mCurQuery != null) { + where = KeychainContract.UserIds.USER_ID + " LIKE ?"; + whereArgs = new String[]{"%" + mCurQuery + "%"}; + } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, SORT_ORDER); + return new CursorLoader(getActivity(), baseUri, PROJECTION, where, whereArgs, SORT_ORDER); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) + mAdapter.setSearchQuery(mCurQuery); mAdapter.swapCursor(data); mStickyList.setAdapter(mAdapter); - // NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading + // NOTE: Not supported by StickyListHeader, but reimplemented here // The list should now be shown. - // if (isResumed()) { - // setListShown(true); - // } else { - // setListShownNoAnimation(true); - // } + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } } @Override @@ -338,6 +368,87 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); } + + @Override + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { + // Get the searchview + MenuItem searchItem = menu.findItem(R.id.menu_key_list_search); + mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem); + + // Execute this when searching + mSearchView.setOnQueryTextListener(this); + + // Erase search result without focus + MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + return true; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + mCurQuery = null; + mSearchView.setQuery("", true); + getLoaderManager().restartLoader(0, null, KeyListFragment.this); + return true; + } + }); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onQueryTextSubmit(String s) { + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + // Called when the action bar search text has changed. Update + // the search filter, and restart the loader to do a new query + // with this filter. + mCurQuery = !TextUtils.isEmpty(s) ? s : null; + getLoaderManager().restartLoader(0, null, this); + return true; + } + + // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 + public void setListShown(boolean shown, boolean animate) { + if (mListShown == shown) { + return; + } + mListShown = shown; + if (shown) { + if (animate) { + mProgressContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_out)); + mListContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_in)); + } + mProgressContainer.setVisibility(View.GONE); + mListContainer.setVisibility(View.VISIBLE); + } else { + if (animate) { + mProgressContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_in)); + mListContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_out)); + } + mProgressContainer.setVisibility(View.VISIBLE); + mListContainer.setVisibility(View.INVISIBLE); + } + } + + // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 + public void setListShown(boolean shown) { + setListShown(shown, true); + } + + // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 + public void setListShownNoAnimation(boolean shown) { + setListShown(shown, false); + } + /** * Implements StickyListHeadersAdapter from library */ @@ -347,6 +458,8 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick private int mIndexIsRevoked; private int mMasterKeyId; + private String mCurQuery; + @SuppressLint("UseSparseArrays") private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>(); @@ -394,12 +507,12 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick String userId = cursor.getString(mIndexUserId); String[] userIdSplit = PgpKeyHelper.splitUserId(userId); if (userIdSplit[0] != null) { - mainUserId.setText(userIdSplit[0]); + mainUserId.setText(highlightSearchQuery(userIdSplit[0])); } else { mainUserId.setText(R.string.user_id_no_name); } if (userIdSplit[1] != null) { - mainUserIdRest.setText(userIdSplit[1]); + mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1])); mainUserIdRest.setVisibility(View.VISIBLE); } else { mainUserIdRest.setVisibility(View.GONE); @@ -558,20 +671,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick notifyDataSetChanged(); } - public boolean isPositionChecked(int position) { - Boolean result = mSelection.get(position); - return result == null ? false : result; - } - - public long[] getCurrentSelectedItemIds() { - long[] ids = new long[mSelection.size()]; - int i = 0; - // get master key ids - for (int pos : mSelection.keySet()) - ids[i++] = mAdapter.getItemId(pos); - return ids; - } - public long[] getCurrentSelectedMasterKeyIds() { long[] ids = new long[mSelection.size()]; int i = 0; @@ -608,6 +707,34 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick return v; } + // search highlight methods + + public void setSearchQuery(String searchQuery) { + mCurQuery = searchQuery; + } + + public String getSearchQuery() { + return mCurQuery; + } + + protected Spannable highlightSearchQuery(String text) { + Spannable highlight = Spannable.Factory.getInstance().newSpannable(text); + + if (mCurQuery != null) { + Pattern pattern = Pattern.compile("(?i)" + mCurQuery); + Matcher matcher = pattern.matcher(text); + if (matcher.find()) { + highlight.setSpan( + new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)), + matcher.start(), + matcher.end(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + return highlight; + } else { + return highlight; + } + } } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java index b38beebd1..2e8f25890 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java @@ -24,24 +24,27 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference; +import android.annotation.SuppressLint; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceActivity; +import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; import android.support.v7.app.ActionBarActivity; +import java.util.List; + +@SuppressLint("NewApi") 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 CheckBoxPreference mForceV3Signatures = null; + + public final static String ACTION_PREFS_GEN = "org.sufficientlysecure.keychain.ui.PREFS_GEN"; + public final static String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV"; + private PreferenceScreen mKeyServerPreference = null; - private Preferences mPreferences; + private static Preferences mPreferences; @Override protected void onCreate(Bundle savedInstanceState) { @@ -53,22 +56,218 @@ public class PreferencesActivity extends PreferenceActivity { // actionBar.setDisplayHomeAsUpEnabled(false); // actionBar.setHomeButtonEnabled(false); - addPreferencesFromResource(R.xml.preferences); + String action = getIntent().getAction(); + + if (action != null && action.equals(ACTION_PREFS_GEN)) { + addPreferencesFromResource(R.xml.gen_preferences); + + initializePassPassPhraceCacheTtl( + (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL)); + + mKeyServerPreference = (PreferenceScreen) findPreference(Constants.pref.KEY_SERVERS); + String servers[] = mPreferences.getKeyServers(); + mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers, + servers.length, servers.length)); + mKeyServerPreference + .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(PreferencesActivity.this, + PreferencesKeyServerActivity.class); + intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS, + mPreferences.getKeyServers()); + startActivityForResult(intent, Id.request.key_server_preference); + return false; + } + }); + + } else if (action != null && action.equals(ACTION_PREFS_ADV)) { + addPreferencesFromResource(R.xml.adv_preferences); + + initializeEncryptionAlgorithm( + (IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM)); + + int[] valueIds = new int[] { Id.choice.compression.none, Id.choice.compression.zip, + Id.choice.compression.zlib, Id.choice.compression.bzip2, }; + String[] entries = new String[] { + getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")", + "ZIP (" + getString(R.string.compression_fast) + ")", + "ZLIB (" + getString(R.string.compression_fast) + ")", + "BZIP2 (" + getString(R.string.compression_very_slow) + ")", }; + String[] values = new String[valueIds.length]; + for (int i = 0; i < values.length; ++i) { + values[i] = "" + valueIds[i]; + } + + initializeHashAlgorithm( + (IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM), + valueIds, entries, values); + + initializeMessageCompression( + (IntegerListPreference) findPreference(Constants.pref.DEFAULT_MESSAGE_COMPRESSION), + valueIds, entries, values); + + initializeFileCompression( + (IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION), + entries, values); + + initializeAsciiArmour((CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR)); + + initializeForceV3Signatures((CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES)); + + } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + // Load the legacy preferences headers + addPreferencesFromResource(R.xml.preference_headers_legacy); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.key_server_preference: { + if (resultCode == RESULT_CANCELED || data == null) { + return; + } + String servers[] = data + .getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS); + mPreferences.setKeyServers(servers); + mKeyServerPreference.setSummary(getResources().getQuantityString( + R.plurals.n_key_servers, servers.length, servers.length)); + break; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + break; + } + } + } + + /* Called only on Honeycomb and later */ + @Override + public void onBuildHeaders(List<Header> target) { + super.onBuildHeaders(target); + loadHeadersFromResource(R.xml.preference_headers, target); + } + + /** This fragment shows the general preferences in android 3.0+ */ + public static class GeneralPrefsFragment extends PreferenceFragment { + + private PreferenceScreen mKeyServerPreference = null; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.gen_preferences); + + initializePassPassPhraceCacheTtl( + (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL)); + + mKeyServerPreference = (PreferenceScreen) findPreference(Constants.pref.KEY_SERVERS); + String servers[] = mPreferences.getKeyServers(); + mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers, + servers.length, servers.length)); + mKeyServerPreference + .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(getActivity(), + PreferencesKeyServerActivity.class); + intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS, + mPreferences.getKeyServers()); + startActivityForResult(intent, Id.request.key_server_preference); + return false; + } + }); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.key_server_preference: { + if (resultCode == RESULT_CANCELED || data == null) { + return; + } + String servers[] = data + .getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS); + mPreferences.setKeyServers(servers); + mKeyServerPreference.setSummary(getResources().getQuantityString( + R.plurals.n_key_servers, servers.length, servers.length)); + break; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + break; + } + } + } + } + + /** This fragment shows the advanced preferences in android 3.0+ */ + public static class AdvancedPrefsFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.adv_preferences); + + initializeEncryptionAlgorithm( + (IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM)); + + int[] valueIds = new int[] { Id.choice.compression.none, Id.choice.compression.zip, + Id.choice.compression.zlib, Id.choice.compression.bzip2, }; + String[] entries = new String[] { + getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")", + "ZIP (" + getString(R.string.compression_fast) + ")", + "ZLIB (" + getString(R.string.compression_fast) + ")", + "BZIP2 (" + getString(R.string.compression_very_slow) + ")", }; + String[] values = new String[valueIds.length]; + for (int i = 0; i < values.length; ++i) { + values[i] = "" + valueIds[i]; + } + + initializeHashAlgorithm( + (IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM), + valueIds, entries, values); + + initializeMessageCompression( + (IntegerListPreference) findPreference(Constants.pref.DEFAULT_MESSAGE_COMPRESSION), + valueIds, entries, values); + + initializeFileCompression( + (IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION), + entries, values); - mPassPhraseCacheTtl = (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL); - mPassPhraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl()); - mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry()); - mPassPhraseCacheTtl + initializeAsciiArmour((CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR)); + + initializeForceV3Signatures((CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES)); + } + } + + protected boolean isValidFragment (String fragmentName) { + return AdvancedPrefsFragment.class.getName().equals(fragmentName) + || GeneralPrefsFragment.class.getName().equals(fragmentName) + || super.isValidFragment(fragmentName); + } + + private static void initializePassPassPhraceCacheTtl(final IntegerListPreference mPassphraseCacheTtl) { + 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()); + mPassphraseCacheTtl.setValue(newValue.toString()); + mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry()); mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString())); return false; } }); + } - mEncryptionAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM); + private static void initializeEncryptionAlgorithm(final IntegerListPreference mEncryptionAlgorithm) { int valueIds[] = { PGPEncryptedData.AES_128, PGPEncryptedData.AES_192, PGPEncryptedData.AES_256, PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH, PGPEncryptedData.CAST5, PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES, @@ -93,8 +292,10 @@ public class PreferencesActivity extends PreferenceActivity { return false; } }); + } - mHashAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM); + private static void initializeHashAlgorithm + (final IntegerListPreference mHashAlgorithm, int[] valueIds, String[] entries, String[] values) { valueIds = new int[] { HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160, HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, }; @@ -116,19 +317,10 @@ public class PreferencesActivity extends PreferenceActivity { 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.compression_fast) + ")", - "ZIP (" + getString(R.string.compression_fast) + ")", - "ZLIB (" + getString(R.string.compression_fast) + ")", - "BZIP2 (" + getString(R.string.compression_very_slow) + ")", }; - values = new String[valueIds.length]; - for (int i = 0; i < values.length; ++i) { - values[i] = "" + valueIds[i]; - } + private static void initializeMessageCompression + (final IntegerListPreference mMessageCompression, int[] valueIds, String[] entries, String[] values) { mMessageCompression.setEntries(entries); mMessageCompression.setEntryValues(values); mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression()); @@ -143,8 +335,10 @@ public class PreferencesActivity extends PreferenceActivity { return false; } }); + } - mFileCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION); + private static void initializeFileCompression + (final IntegerListPreference mFileCompression, String[] entries, String[] values) { mFileCompression.setEntries(entries); mFileCompression.setEntryValues(values); mFileCompression.setValue("" + mPreferences.getDefaultFileCompression()); @@ -157,8 +351,9 @@ public class PreferencesActivity extends PreferenceActivity { return false; } }); + } - mAsciiArmour = (CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR); + private static void initializeAsciiArmour(final CheckBoxPreference mAsciiArmour) { mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour()); mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -167,8 +362,9 @@ public class PreferencesActivity extends PreferenceActivity { return false; } }); + } - mForceV3Signatures = (CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES); + private static void initializeForceV3Signatures(final CheckBoxPreference mForceV3Signatures) { mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures()); mForceV3Signatures .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @@ -178,43 +374,5 @@ public class PreferencesActivity extends PreferenceActivity { return false; } }); - - mKeyServerPreference = (PreferenceScreen) findPreference(Constants.pref.KEY_SERVERS); - String servers[] = mPreferences.getKeyServers(); - mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers, - servers.length, servers.length)); - mKeyServerPreference - .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - Intent intent = new Intent(PreferencesActivity.this, - PreferencesKeyServerActivity.class); - intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS, - mPreferences.getKeyServers()); - startActivityForResult(intent, Id.request.key_server_preference); - return false; - } - }); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.key_server_preference: { - if (resultCode == RESULT_CANCELED || data == null) { - return; - } - String servers[] = data - .getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS); - mPreferences.setKeyServers(servers); - mKeyServerPreference.setSummary(getResources().getQuantityString( - R.plurals.n_key_servers, servers.length, servers.length)); - break; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - break; - } - } } -} +}
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java index b5ac739ae..50fec1ffc 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java @@ -50,14 +50,14 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O super.onCreate(savedInstanceState); // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, + ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done, new View.OnClickListener() { @Override public void onClick(View v) { // ok okClicked(); } - }, R.string.btn_do_not_save, new View.OnClickListener() { + }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() { @Override public void onClick(View v) { // cancel @@ -81,11 +81,11 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O Intent intent = getIntent(); String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS); if (servers != null) { - for (int i = 0; i < servers.length; ++i) { + for (String serv : servers) { KeyServerEditor view = (KeyServerEditor) mInflater.inflate( R.layout.key_server_editor, mEditors, false); view.setEditorListener(this); - view.setValue(servers[i]); + view.setValue(serv); mEditors.addView(view); } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java index 86ae0073f..3c63628f7 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java @@ -46,14 +46,14 @@ public class SelectPublicKeyActivity extends ActionBarActivity { super.onCreate(savedInstanceState); // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, + ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done, new View.OnClickListener() { @Override public void onClick(View v) { // ok okClicked(); } - }, R.string.btn_do_not_save, new View.OnClickListener() { + }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() { @Override public void onClick(View v) { // cancel diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java index 59b46dd00..d320af451 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java @@ -30,7 +30,7 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; -import android.app.Activity; +import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; import android.net.Uri; @@ -38,17 +38,35 @@ import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; -public class SelectPublicKeyFragment extends ListFragmentWorkaround implements +public class SelectPublicKeyFragment extends ListFragmentWorkaround implements TextWatcher, LoaderManager.LoaderCallbacks<Cursor> { public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids"; - private Activity mActivity; private SelectKeyCursorAdapter mAdapter; - private ListView mListView; - + private EditText mSearchView; private long mSelectedMasterKeyIds[]; + private String mCurQuery; + + // copied from ListFragment + static final int INTERNAL_EMPTY_ID = 0x00ff0001; + static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002; + static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003; + // added for search view + static final int SEARCH_ID = 0x00ff0004; /** * Creates new instance of this fragment @@ -67,27 +85,100 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mSelectedMasterKeyIds = getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS); } /** + * Copied from ListFragment and added EditText for search on top of list. + * We do not use a custom layout here, because this breaks the progress bar functionality + * of ListFragment. + * + * @param inflater + * @param container + * @param savedInstanceState + * @return + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final Context context = getActivity(); + + FrameLayout root = new FrameLayout(context); + + // ------------------------------------------------------------------ + + LinearLayout pframe = new LinearLayout(context); + pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID); + pframe.setOrientation(LinearLayout.VERTICAL); + pframe.setVisibility(View.GONE); + pframe.setGravity(Gravity.CENTER); + + ProgressBar progress = new ProgressBar(context, null, + android.R.attr.progressBarStyleLarge); + pframe.addView(progress, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + root.addView(pframe, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + // ------------------------------------------------------------------ + + FrameLayout lframe = new FrameLayout(context); + lframe.setId(INTERNAL_LIST_CONTAINER_ID); + + TextView tv = new TextView(getActivity()); + tv.setId(INTERNAL_EMPTY_ID); + tv.setGravity(Gravity.CENTER); + lframe.addView(tv, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + // Added for search view: linearLayout, mSearchView + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + mSearchView = new EditText(context); + mSearchView.setId(SEARCH_ID); + mSearchView.setHint(R.string.menu_search); + mSearchView.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.ic_action_search), null, null, null); + + linearLayout.addView(mSearchView, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + ListView lv = new ListView(getActivity()); + lv.setId(android.R.id.list); + lv.setDrawSelectorOnTop(false); + linearLayout.addView(lv, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + lframe.addView(linearLayout, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + root.addView(lframe, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + // ------------------------------------------------------------------ + + root.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + return root; + } + + /** * Define Adapter and Loader on create of Activity */ @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mActivity = getActivity(); - mListView = getListView(); - - mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); // Give some text to display if there is no data. In a real // application this would come from a resource. setEmptyText(getString(R.string.list_empty)); - mAdapter = new SelectKeyCursorAdapter(mActivity, null, 0, mListView, Id.type.public_key); + mSearchView.addTextChangedListener(this); + + mAdapter = new SelectKeyCursorAdapter(getActivity(), null, 0, getListView(), Id.type.public_key); setListAdapter(mAdapter); @@ -101,16 +192,16 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements /** * Selects items based on master key ids in list view - * + * * @param masterKeyIds */ private void preselectMasterKeyIds(long[] masterKeyIds) { if (masterKeyIds != null) { - for (int i = 0; i < mListView.getCount(); ++i) { + for (int i = 0; i < getListView().getCount(); ++i) { long keyId = mAdapter.getMasterKeyId(i); - for (int j = 0; j < masterKeyIds.length; ++j) { - if (keyId == masterKeyIds[j]) { - mListView.setItemChecked(i, true); + for (long masterKeyId : masterKeyIds) { + if (keyId == masterKeyId) { + getListView().setItemChecked(i, true); break; } } @@ -120,15 +211,15 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements /** * Returns all selected master key ids - * + * * @return */ public long[] getSelectedMasterKeyIds() { // mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key // ids! Vector<Long> vector = new Vector<Long>(); - for (int i = 0; i < mListView.getCount(); ++i) { - if (mListView.isItemChecked(i)) { + for (int i = 0; i < getListView().getCount(); ++i) { + if (getListView().isItemChecked(i)) { vector.add(mAdapter.getMasterKeyId(i)); } } @@ -144,13 +235,13 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements /** * Returns all selected user ids - * + * * @return */ public String[] getSelectedUserIds() { Vector<String> userIds = new Vector<String>(); - for (int i = 0; i < mListView.getCount(); ++i) { - if (mListView.isItemChecked(i)) { + for (int i = 0; i < getListView().getCount(); ++i) { + if (getListView().isItemChecked(i)) { userIds.add((String) mAdapter.getUserId(i)); } } @@ -168,7 +259,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements // These are the rows that we will retrieve. long now = new Date().getTime() / 1000; - String[] projection = new String[] { + String[] projection = new String[]{ KeyRings._ID, KeyRings.MASTER_KEY_ID, UserIds.USER_ID, @@ -185,7 +276,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements + Keys.CAN_ENCRYPT + " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND " + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY + " >= '" + now + "')) AS " - + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, }; + + SelectKeyCursorAdapter.PROJECTION_ROW_VALID,}; String inMasterKeyList = null; if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) { @@ -199,37 +290,28 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements inMasterKeyList += ")"; } - // 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.USER_ID + " ASC"; if (inMasterKeyList != null) { // sort by selected master keys orderBy = inMasterKeyList + " DESC, " + orderBy; } + String where = null; + String whereArgs[] = null; + if (mCurQuery != null) { + where = UserIds.USER_ID + " LIKE ?"; + whereArgs = new String[]{"%" + mCurQuery + "%"}; + } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, projection, null, null, orderBy); + return new CursorLoader(getActivity(), baseUri, projection, where, whereArgs, orderBy); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) + mAdapter.setSearchQuery(mCurQuery); mAdapter.swapCursor(data); // The list should now be shown. @@ -250,4 +332,20 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements // longer using it. mAdapter.swapCursor(null); } + + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void afterTextChanged(Editable editable) { + mCurQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null; + getLoaderManager().restartLoader(0, null, this); + } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java index 6bcb84f46..c9129285e 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java @@ -24,10 +24,13 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import android.Manifest; import android.app.Activity; +import android.app.ActivityOptions; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -40,6 +43,7 @@ public class SelectSecretKeyLayoutFragment extends Fragment { private TextView mKeyUserId; private TextView mKeyUserIdRest; + private TextView mKeyMasterKeyIdHex; private BootstrapButton mSelectKeyButton; private Boolean mFilterCertify; @@ -61,26 +65,52 @@ public class SelectSecretKeyLayoutFragment extends Fragment { public void selectKey(long secretKeyId) { if (secretKeyId == Id.key.none) { - mKeyUserId.setText(R.string.api_settings_no_key); + mKeyMasterKeyIdHex.setText(R.string.api_settings_no_key); mKeyUserIdRest.setText(""); + mKeyUserId.setVisibility(View.GONE); + mKeyUserIdRest.setVisibility(View.GONE); + } else { - String uid = getResources().getString(R.string.user_id_no_name); - String uidExtra = ""; PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId( getActivity(), secretKeyId); if (keyRing != null) { PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing); + String masterkeyIdHex = PgpKeyHelper.convertKeyIdToHex(secretKeyId); + if (key != null) { String userId = PgpKeyHelper.getMainUserIdSafe(getActivity(), key); - String chunks[] = userId.split(" <", 2); - uid = chunks[0]; - if (chunks.length > 1) { - uidExtra = "<" + chunks[1]; + + String[] userIdSplit = PgpKeyHelper.splitUserId(userId); + String userName, userEmail; + + if (userIdSplit[0] != null) { + userName = userIdSplit[0]; + } else { + userName = getActivity().getResources().getString(R.string.user_id_no_name); } + + if (userIdSplit[1] != null) { + userEmail = userIdSplit[1]; + } else { + userEmail = getActivity().getResources().getString(R.string.error_user_id_no_email); + } + + mKeyMasterKeyIdHex.setText(masterkeyIdHex); + mKeyUserId.setText(userName); + mKeyUserIdRest.setText(userEmail); + mKeyUserId.setVisibility(View.VISIBLE); + mKeyUserIdRest.setVisibility(View.VISIBLE); + } else { + mKeyMasterKeyIdHex.setText(getActivity().getResources().getString(R.string.no_key)); + mKeyUserId.setVisibility(View.GONE); + mKeyUserIdRest.setVisibility(View.GONE); } + } else { + mKeyMasterKeyIdHex.setText(getActivity().getResources().getString(R.string.no_keys_added_or_updated) + " for master id: " + secretKeyId); + mKeyUserId.setVisibility(View.GONE); + mKeyUserIdRest.setVisibility(View.GONE); } - mKeyUserId.setText(uid); - mKeyUserIdRest.setText(uidExtra); + } } @@ -98,6 +128,7 @@ public class SelectSecretKeyLayoutFragment extends Fragment { mKeyUserId = (TextView) view.findViewById(R.id.select_secret_key_user_id); mKeyUserIdRest = (TextView) view.findViewById(R.id.select_secret_key_user_id_rest); + mKeyMasterKeyIdHex = (TextView) view.findViewById(R.id.select_secret_key_master_key_hex); mSelectKeyButton = (BootstrapButton) view .findViewById(R.id.select_secret_key_select_key_button); mFilterCertify = false; @@ -117,30 +148,31 @@ public class SelectSecretKeyLayoutFragment extends Fragment { startActivityForResult(intent, REQUEST_CODE_SELECT_KEY); } + // Select Secret Key Activity delivers the intent which was sent by it using interface to Select + // Secret Key Fragment.Intent contains Master Key Id, User Email, User Name, Master Key Id Hex. @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode & 0xFFFF) { - case REQUEST_CODE_SELECT_KEY: { - long secretKeyId; - if (resultCode == Activity.RESULT_OK) { - Bundle bundle = data.getExtras(); - secretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID); - - selectKey(secretKeyId); - - // remove displayed errors - mKeyUserId.setError(null); - - // give value back to callback - mCallback.onKeySelected(secretKeyId); + case REQUEST_CODE_SELECT_KEY: { + long secretKeyId; + if (resultCode == Activity.RESULT_OK) { + Bundle bundle = data.getExtras(); + secretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID); + selectKey(secretKeyId); + + // remove displayed errors + mKeyUserId.setError(null); + + // give value back to callback + mCallback.onKeySelected(secretKeyId); + } + break; } - break; - } - default: - super.onActivityResult(requestCode, resultCode, data); + default: + super.onActivityResult(requestCode, resultCode, data); - break; + break; } } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index 550d3047d..6f0aaa0f0 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -102,7 +102,7 @@ public class UploadKeyActivity extends ActionBarActivity { // Message is received after uploading is done in ApgService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, - R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) { + getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { // handle messages by standard ApgHandler first super.handleMessage(message); @@ -113,7 +113,7 @@ public class UploadKeyActivity extends ActionBarActivity { Toast.LENGTH_SHORT).show(); finish(); } - }; + } }; // Create a new Messenger for the communication back diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 055203c0f..01a2740b1 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -93,12 +93,12 @@ public class ViewKeyActivity extends ActionBarActivity { Bundle mainBundle = new Bundle(); mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_main)), - ViewKeyMainFragment.class, mainBundle, (selectedTab == 0 ? true : false)); + ViewKeyMainFragment.class, mainBundle, (selectedTab == 0)); Bundle certBundle = new Bundle(); certBundle.putLong(ViewKeyCertsFragment.ARG_KEYRING_ROW_ID, rowId); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_certs)), - ViewKeyCertsFragment.class, certBundle, (selectedTab == 1 ? true : false)); + ViewKeyCertsFragment.class, certBundle, (selectedTab == 1)); } @Override @@ -123,8 +123,8 @@ public class ViewKeyActivity extends ActionBarActivity { uploadToKeyserver(mDataUri); return true; case R.id.menu_key_view_export_file: - mExportHelper.showExportKeysDialog(mDataUri, Id.type.public_key, Constants.path.APP_DIR - + "/pubexport.asc"); + long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())}; + mExportHelper.showExportKeysDialog(ids, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB); return true; case R.id.menu_key_view_share_default_fingerprint: shareKey(mDataUri, true); @@ -159,7 +159,7 @@ public class ViewKeyActivity extends ActionBarActivity { } private void updateFromKeyserver(Uri dataUri) { - long updateKeyId = ProviderHelper.getMasterKeyId(ViewKeyActivity.this, mDataUri); + long updateKeyId = ProviderHelper.getMasterKeyId(ViewKeyActivity.this, dataUri); if (updateKeyId == 0) { Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!"); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java index 00ca0d1f4..c898a06ea 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java @@ -26,7 +26,10 @@ import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; +import android.text.Spannable; +import android.text.SpannableStringBuilder; import android.text.format.DateFormat; +import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -273,7 +276,7 @@ public class ViewKeyMainFragment extends Fragment implements // get key id from MASTER_KEY_ID long keyId = data.getLong(KEYS_INDEX_KEY_ID); - String keyIdStr = "0x" + PgpKeyHelper.convertKeyIdToHex(keyId); + String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId); mKeyId.setText(keyIdStr); // get creation date from CREATION @@ -306,9 +309,8 @@ public class ViewKeyMainFragment extends Fragment implements fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), mDataUri); } String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true); - fingerprint = fingerprint.replace(" ", "\n"); - mFingerprint.setText(fingerprint); + mFingerprint.setText(colorizeFingerprint(fingerprint)); } mKeysAdapter.swapCursor(data); @@ -319,6 +321,25 @@ public class ViewKeyMainFragment extends Fragment implements } } + private SpannableStringBuilder colorizeFingerprint(String fingerprint) { + SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint); + ForegroundColorSpan fcs = new ForegroundColorSpan(Color.BLACK); + + // for each 4 characters of the fingerprint + 1 space + for (int i = 0; i < fingerprint.length(); i += 5) { + int minFingLength = Math.min(i + 4, fingerprint.length()); + String fourChars = fingerprint.substring(i, minFingLength); + + // Create a foreground color by converting the 4 fingerprint chars to an int hashcode + // and then converting that int to hex to use as a color + fcs = new ForegroundColorSpan( + Color.parseColor(String.format("#%06X", (0xFFFFFF & fourChars.hashCode())))); + sb.setSpan(fcs, i, minFingLength, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } + + return sb; + } + /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java new file mode 100644 index 000000000..fd7a2dc30 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.support.v4.widget.CursorAdapter; +import android.text.Spannable; +import android.text.style.ForegroundColorSpan; + +import org.sufficientlysecure.keychain.R; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class HighlightQueryCursorAdapter extends CursorAdapter { + + private String mCurQuery; + + public HighlightQueryCursorAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + mCurQuery = null; + } + + public void setSearchQuery(String searchQuery) { + mCurQuery = searchQuery; + } + + public String getSearchQuery() { + return mCurQuery; + } + + protected Spannable highlightSearchQuery(String text) { + Spannable highlight = Spannable.Factory.getInstance().newSpannable(text); + + if (mCurQuery != null) { + Pattern pattern = Pattern.compile("(?i)" + mCurQuery); + Matcher matcher = pattern.matcher(text); + if (matcher.find()) { + highlight.setSpan( + new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)), + matcher.start(), + matcher.end(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + return highlight; + } else { + return highlight; + } + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 52186b662..4f7623bce 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -42,7 +42,15 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { protected Activity mActivity; protected List<ImportKeysListEntry> data; + static class ViewHolder{ + private TextView mainUserId; + private TextView mainUserIdRest; + private TextView keyId; + private TextView fingerprint; + private TextView algorithm; + private TextView status; + } public ImportKeysAdapter(Activity activity) { super(activity, -1); mActivity = activity; @@ -86,16 +94,21 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { public View getView(int position, View convertView, ViewGroup parent) { ImportKeysListEntry entry = data.get(position); - - View view = mInflater.inflate(R.layout.import_keys_list_entry, null); - - TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - TextView keyId = (TextView) view.findViewById(R.id.keyId); - TextView fingerprint = (TextView) view.findViewById(R.id.fingerprint); - TextView algorithm = (TextView) view.findViewById(R.id.algorithm); - TextView status = (TextView) view.findViewById(R.id.status); - + ViewHolder holder; + if(convertView == null) { + holder = new ViewHolder(); + convertView = mInflater.inflate(R.layout.import_keys_list_entry, null); + holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId); + holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest); + holder.keyId = (TextView) convertView.findViewById(R.id.keyId); + holder.fingerprint = (TextView) convertView.findViewById(R.id.fingerprint); + holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm); + holder.status = (TextView) convertView.findViewById(R.id.status); + convertView.setTag(holder); + } + else{ + holder = (ViewHolder)convertView.getTag(); + } // main user id String userId = entry.userIds.get(0); String[] userIdSplit = PgpKeyHelper.splitUserId(userId); @@ -105,39 +118,39 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { // show red user id if it is a secret key if (entry.secretKey) { userIdSplit[0] = mActivity.getString(R.string.secret_key) + " " + userIdSplit[0]; - mainUserId.setTextColor(Color.RED); + holder.mainUserId.setTextColor(Color.RED); } - mainUserId.setText(userIdSplit[0]); + holder.mainUserId.setText(userIdSplit[0]); } else { - mainUserId.setText(R.string.user_id_no_name); + holder.mainUserId.setText(R.string.user_id_no_name); } // email if (userIdSplit[1] != null) { - mainUserIdRest.setText(userIdSplit[1]); - mainUserIdRest.setVisibility(View.VISIBLE); + holder.mainUserIdRest.setText(userIdSplit[1]); + holder.mainUserIdRest.setVisibility(View.VISIBLE); } else { - mainUserIdRest.setVisibility(View.GONE); + holder.mainUserIdRest.setVisibility(View.GONE); } - keyId.setText(entry.hexKeyId); + holder.keyId.setText(entry.hexKeyId); if (entry.fingerPrint != null) { - fingerprint.setText(mActivity.getString(R.string.fingerprint) + " " + entry.fingerPrint); - fingerprint.setVisibility(View.VISIBLE); + holder.fingerprint.setText(mActivity.getString(R.string.fingerprint) + " " + entry.fingerPrint); + holder.fingerprint.setVisibility(View.VISIBLE); } else { - fingerprint.setVisibility(View.GONE); + holder.fingerprint.setVisibility(View.GONE); } - algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm); + holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm); if (entry.revoked) { - status.setText(R.string.revoked); + holder.status.setText(R.string.revoked); } else { - status.setVisibility(View.GONE); + holder.status.setVisibility(View.GONE); } - LinearLayout ll = (LinearLayout) view.findViewById(R.id.list); + LinearLayout ll = (LinearLayout) convertView.findViewById(R.id.list); if (entry.userIds.size() == 1) { ll.setVisibility(View.GONE); } else { @@ -162,10 +175,10 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { } } - CheckBox cBox = (CheckBox) view.findViewById(R.id.selected); + CheckBox cBox = (CheckBox) convertView.findViewById(R.id.selected); cBox.setChecked(entry.isSelected()); - return view; + return convertView; } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java index 4a7a9c93a..a52e9b447 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java @@ -125,7 +125,10 @@ public class ImportKeysListEntry implements Serializable, Parcelable { * Constructor for later querying from keyserver */ public ImportKeysListEntry() { + // keys from keyserver are always public keys secretKey = false; + // do not select by default + selected = false; userIds = new ArrayList<String>(); } @@ -167,7 +170,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { this.revoked = pgpKeyRing.getPublicKey().isRevoked(); this.fingerPrint = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey() .getFingerprint(), true); - this.hexKeyId = "0x" + PgpKeyHelper.convertKeyIdToHex(keyId); + this.hexKeyId = PgpKeyHelper.convertKeyIdToHex(keyId); this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength(); int algorithm = pgpKeyRing.getPublicKey().getAlgorithm(); if (algorithm == PGPPublicKey.RSA_ENCRYPT || algorithm == PGPPublicKey.RSA_GENERAL diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java index 29e418db7..3eca99f15 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java @@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.ui.adapter; import java.io.BufferedInputStream; import java.io.InputStream; import java.util.ArrayList; -import java.util.List; import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPObjectFactory; @@ -34,6 +33,21 @@ import android.content.Context; import android.support.v4.content.AsyncTaskLoader; public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { + + public static class FileHasNoContent extends Exception { + + } + + public static class NonPgpPart extends Exception { + private int count; + public NonPgpPart(int count) { + this.count = count; + } + public int getCount() { + return count; + } + } + Context mContext; InputData mInputData; @@ -88,21 +102,26 @@ public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper /** * Reads all PGPKeyRing objects from input * - * @param keyringBytes + * @param inputData * @return */ private void generateListOfKeyrings(InputData inputData) { + + boolean isEmpty = true; + int nonPgpCounter = 0; + PositionAwareInputStream progressIn = new PositionAwareInputStream( inputData.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 + // armor blocks BufferedInputStream bufferedInput = new BufferedInputStream(progressIn); try { // read all available blocks... (asc files can contain many blocks with BEGIN END) while (bufferedInput.available() > 0) { + isEmpty = false; InputStream in = PGPUtil.getDecoderStream(bufferedInput); PGPObjectFactory objectFactory = new PGPObjectFactory(in); @@ -116,11 +135,25 @@ public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper addToData(newKeyring); } else { Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!"); + nonPgpCounter++; } } } } catch (Exception e) { Log.e(Constants.TAG, "Exception on parsing key file!", e); + entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(data, e); + nonPgpCounter = 0; + } + + if(isEmpty) { + Log.e(Constants.TAG, "File has no content!", new FileHasNoContent()); + entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> + (data, new FileHasNoContent()); + } + + if(nonPgpCounter > 0) { + entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> + (data, new NonPgpPart(nonPgpCounter)); } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index d44dd5890..6d67a0e65 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -25,7 +25,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import android.content.Context; import android.database.Cursor; -import android.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -33,7 +32,9 @@ import android.widget.CheckBox; import android.widget.ListView; import android.widget.TextView; -public class SelectKeyCursorAdapter extends CursorAdapter { + + +public class SelectKeyCursorAdapter extends HighlightQueryCursorAdapter { protected int mKeyType; @@ -55,7 +56,6 @@ public class SelectKeyCursorAdapter extends CursorAdapter { mInflater = LayoutInflater.from(context); mListView = listView; mKeyType = keyType; - initIndex(c); } @@ -104,12 +104,12 @@ public class SelectKeyCursorAdapter extends CursorAdapter { String[] userIdSplit = PgpKeyHelper.splitUserId(userId); if (userIdSplit[0] != null) { - mainUserId.setText(userIdSplit[0]); + mainUserId.setText(highlightSearchQuery(userIdSplit[0])); } else { mainUserId.setText(R.string.user_id_no_name); } if (userIdSplit[1] != null) { - mainUserIdRest.setText(userIdSplit[1]); + mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1])); } else { mainUserIdRest.setText(""); } @@ -164,5 +164,4 @@ public class SelectKeyCursorAdapter extends CursorAdapter { public View newView(Context context, Cursor cursor, ViewGroup parent) { return mInflater.inflate(R.layout.select_key_item, null); } - } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java index 54c7eb60e..046a98883 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java @@ -83,7 +83,7 @@ public class ViewKeyKeysAdapter extends CursorAdapter { ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); - String keyIdStr = "0x" + PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId)); + String keyIdStr = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId)); String algorithmStr = PgpKeyHelper.getAlgorithmInfo(cursor.getInt(mIndexAlgorithm), cursor.getInt(mIndexKeySize)); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java index 98b677511..a47601c9b 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java @@ -32,6 +32,7 @@ import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Choice; +import java.util.ArrayList; import java.util.Vector; public class CreateKeyDialogFragment extends DialogFragment { @@ -78,7 +79,7 @@ public class CreateKeyDialogFragment extends DialogFragment { boolean wouldBeMasterKey = (childCount == 0); final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm); - Vector<Choice> choices = new Vector<Choice>(); + ArrayList<Choice> choices = new ArrayList<Choice>(); choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString( R.string.dsa))); if (!wouldBeMasterKey) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java index cd8bc79a9..162bf32fd 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java @@ -67,7 +67,7 @@ public class DeleteFileDialogFragment extends DialogFragment { alert.setMessage(this.getString(R.string.file_delete_confirmation, deleteFile)); alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - + @Override public void onClick(DialogInterface dialog, int id) { dismiss(); @@ -83,7 +83,10 @@ public class DeleteFileDialogFragment extends DialogFragment { intent.putExtra(KeychainIntentService.EXTRA_DATA, data); ProgressDialogFragment deletingDialog = ProgressDialogFragment.newInstance( - R.string.progress_deleting_securely, ProgressDialog.STYLE_HORIZONTAL, false, null); + getString(R.string.progress_deleting_securely), + ProgressDialog.STYLE_HORIZONTAL, + false, + null); // Message is received after deleting is done in ApgService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(activity, deletingDialog) { @@ -95,7 +98,7 @@ public class DeleteFileDialogFragment extends DialogFragment { Toast.makeText(activity, R.string.file_delete_successful, Toast.LENGTH_SHORT).show(); } - }; + } }; // Create a new Messenger for the communication back diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java index 39ce63b5f..2a3a7508d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java @@ -33,7 +33,6 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.DialogFragment; -import android.text.Html; import android.view.LayoutInflater; import android.view.View; import android.widget.CheckBox; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java index e88271240..afa05cc91 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java @@ -153,17 +153,17 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor dismiss(); long curKeyIndex = 1; boolean keyOK = true; - String passPhrase = mPassphraseEditText.getText().toString(); + String passphrase = mPassphraseEditText.getText().toString(); long keyId; PGPSecretKey clickSecretKey = secretKey; if (clickSecretKey != null) { - while (keyOK == true) { + while (keyOK) { if (clickSecretKey != null) { // check again for loop try { PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - passPhrase.toCharArray()); + passphrase.toCharArray()); PGPPrivateKey testKey = clickSecretKey .extractPrivateKey(keyDecryptor); if (testKey == null) { @@ -206,10 +206,10 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor // cache the new passphrase Log.d(Constants.TAG, "Everything okay! Caching entered passphrase"); - PassphraseCacheService.addCachedPassphrase(activity, keyId, passPhrase); - if (keyOK == false && clickSecretKey.getKeyID() != keyId) { + PassphraseCacheService.addCachedPassphrase(activity, keyId, passphrase); + if ( !keyOK && clickSecretKey.getKeyID() != keyId) { PassphraseCacheService.addCachedPassphrase(activity, clickSecretKey.getKeyID(), - passPhrase); + passphrase); } sendMessageToHandler(MESSAGE_OKAY); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java index 6c62d14e0..b7a1882ef 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java @@ -30,7 +30,7 @@ import android.view.KeyEvent; import org.sufficientlysecure.keychain.R; public class ProgressDialogFragment extends DialogFragment { - private static final String ARG_MESSAGE_ID = "message_id"; + private static final String ARG_MESSAGE = "message"; private static final String ARG_STYLE = "style"; private static final String ARG_CANCELABLE = "cancelable"; @@ -39,16 +39,16 @@ public class ProgressDialogFragment extends DialogFragment { /** * Creates new instance of this fragment * - * @param messageId + * @param message * @param style * @param cancelable * @return */ - public static ProgressDialogFragment newInstance(int messageId, int style, boolean cancelable, + public static ProgressDialogFragment newInstance(String message, int style, boolean cancelable, OnCancelListener onCancelListener) { ProgressDialogFragment frag = new ProgressDialogFragment(); Bundle args = new Bundle(); - args.putInt(ARG_MESSAGE_ID, messageId); + args.putString(ARG_MESSAGE, message); args.putInt(ARG_STYLE, style); args.putBoolean(ARG_CANCELABLE, cancelable); @@ -117,22 +117,22 @@ public class ProgressDialogFragment extends DialogFragment { dialog.setCancelable(false); dialog.setCanceledOnTouchOutside(false); - int messageId = getArguments().getInt(ARG_MESSAGE_ID); + String message = getArguments().getString(ARG_MESSAGE); int style = getArguments().getInt(ARG_STYLE); boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE); - dialog.setMessage(getString(messageId)); + dialog.setMessage(message); dialog.setProgressStyle(style); if (cancelable) { dialog.setButton(DialogInterface.BUTTON_NEGATIVE, activity.getString(R.string.progress_cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }); + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); } // Disable the back button @@ -140,7 +140,6 @@ public class ProgressDialogFragment extends DialogFragment { @Override public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { return true; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java index 50e72dfba..e406547b3 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java @@ -101,9 +101,9 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi public void onClick(DialogInterface dialog, int id) { dismiss(); - String passPhrase1 = mPassphraseEditText.getText().toString(); - String passPhrase2 = mPassphraseAgainEditText.getText().toString(); - if (!passPhrase1.equals(passPhrase2)) { + String passphrase1 = mPassphraseEditText.getText().toString(); + String passphrase2 = mPassphraseAgainEditText.getText().toString(); + if (!passphrase1.equals(passphrase2)) { Toast.makeText( activity, getString(R.string.error_message, @@ -112,7 +112,7 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi return; } - if (passPhrase1.equals("")) { + if (passphrase1.equals("")) { Toast.makeText( activity, getString(R.string.error_message, @@ -123,7 +123,7 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi // return resulting data back to activity Bundle data = new Bundle(); - data.putString(MESSAGE_NEW_PASSPHRASE, passPhrase1); + data.putString(MESSAGE_NEW_PASSPHRASE, passphrase1); sendMessageToHandler(MESSAGE_OKAY, data); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java index 03e09cdcb..b850638a6 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java @@ -53,7 +53,6 @@ public class ShareNfcDialogFragment extends DialogFragment { AlertDialog.Builder alert = new AlertDialog.Builder(activity); - alert.setIcon(android.R.drawable.ic_dialog_info); alert.setTitle(R.string.share_nfc_dialog); alert.setCancelable(true); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java index 6c265057e..65461cb4f 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java @@ -34,6 +34,7 @@ import android.app.DatePickerDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; +import android.text.format.DateUtils; import android.util.AttributeSet; import android.view.View; import android.view.View.OnClickListener; @@ -58,6 +59,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { Spinner mUsage; TextView mCreationDate; BootstrapButton mExpiryDateButton; + GregorianCalendar mCreatedDate; GregorianCalendar mExpiryDate; private int mDatePickerResultCount = 0; @@ -113,8 +115,12 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { if (date == null) { date = new GregorianCalendar(TimeZone.getTimeZone("UTC")); } - - DatePickerDialog dialog = new DatePickerDialog(getContext(), + /* + * Using custom DatePickerDialog which overrides the setTitle because + * the DatePickerDialog title is buggy (unix warparound bug). + * See: https://code.google.com/p/android/issues/detail?id=49066 + */ + DatePickerDialog dialog = new ExpiryDatePickerDialog(getContext(), mExpiryDateSetListener, date.get(Calendar.YEAR), date.get(Calendar.MONTH), date.get(Calendar.DAY_OF_MONTH)); mDatePickerResultCount = 0; @@ -129,6 +135,21 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { } } }); + + // setCalendarViewShown() is supported from API 11 onwards. + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) + // Hide calendarView in tablets because of the unix warparound bug. + dialog.getDatePicker().setCalendarViewShown(false); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + if ( dialog != null && mCreatedDate != null ) { + dialog.getDatePicker().setMinDate(mCreatedDate.getTime().getTime()+ DateUtils.DAY_IN_MILLIS); + } else { + //When created date isn't available + dialog.getDatePicker().setMinDate(date.getTime().getTime()+ DateUtils.DAY_IN_MILLIS); + } + } + dialog.show(); } }); @@ -153,9 +174,8 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { } mAlgorithm.setText(PgpKeyHelper.getAlgorithmInfo(key)); - String keyId1Str = PgpKeyHelper.convertKeyIdToHex(key.getKeyID()); - String keyId2Str = PgpKeyHelper.convertKeyIdToHex(key.getKeyID() >> 32); - mKeyId.setText(keyId1Str + " " + keyId2Str); + String keyIdStr = PgpKeyHelper.convertKeyIdToHex(key.getKeyID()); + mKeyId.setText(keyIdStr); Vector<Choice> choices = new Vector<Choice>(); boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT); @@ -205,7 +225,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); cal.setTime(PgpKeyHelper.getCreationDate(key)); - mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime())); + setCreatedDate(cal); cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); Date expiryDate = PgpKeyHelper.getExpiryDate(key); if (expiryDate == null) { @@ -235,6 +255,15 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { mEditorListener = listener; } + private void setCreatedDate(GregorianCalendar date) { + mCreatedDate = date; + if (date == null) { + mCreationDate.setText(getContext().getString(R.string.none)); + } else { + mCreationDate.setText(DateFormat.getDateInstance().format(date.getTime())); + } + } + private void setExpiryDate(GregorianCalendar date) { mExpiryDate = date; if (date == null) { @@ -253,3 +282,14 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { } } + +class ExpiryDatePickerDialog extends DatePickerDialog { + + public ExpiryDatePickerDialog(Context context, OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) { + super(context, callBack, year, monthOfYear, dayOfMonth); + } + //Set permanent title. + public void setTitle(CharSequence title) { + super.setTitle(getContext().getString(R.string.expiry_date_dialog_title)); + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java index a95d80a4e..0acfe13bc 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java @@ -80,19 +80,19 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor public void setType(int type) { mType = type; switch (type) { - case Id.type.user_id: { - mTitle.setText(R.string.section_user_ids); - break; - } + case Id.type.user_id: { + mTitle.setText(R.string.section_user_ids); + break; + } - case Id.type.key: { - mTitle.setText(R.string.section_keys); - break; - } + case Id.type.key: { + mTitle.setText(R.string.section_keys); + break; + } - default: { - break; - } + default: { + break; + } } } @@ -103,7 +103,9 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor } } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override protected void onFinishInflate() { mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -121,7 +123,9 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor super.onFinishInflate(); } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ public void onDeleted(Editor editor) { this.updateEditorsVisible(); } @@ -131,38 +135,40 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE); } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ public void onClick(View v) { if (canEdit) { 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); + 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; } - mEditors.addView(view); - break; - } - case Id.type.key: { - CreateKeyDialogFragment mCreateKeyDialogFragment = CreateKeyDialogFragment.newInstance(mEditors.getChildCount()); - mCreateKeyDialogFragment.setOnAlgorithmSelectedListener(new CreateKeyDialogFragment.OnAlgorithmSelectedListener() { - @Override - public void onAlgorithmSelected(Choice algorithmChoice, int keySize) { - mNewKeyAlgorithmChoice = algorithmChoice; - mNewKeySize = keySize; - createKey(); - } - }); - mCreateKeyDialogFragment.show(mActivity.getSupportFragmentManager(), "createKeyDialog"); - break; - } + case Id.type.key: { + CreateKeyDialogFragment mCreateKeyDialogFragment = CreateKeyDialogFragment.newInstance(mEditors.getChildCount()); + mCreateKeyDialogFragment.setOnAlgorithmSelectedListener(new CreateKeyDialogFragment.OnAlgorithmSelectedListener() { + @Override + public void onAlgorithmSelected(Choice algorithmChoice, int keySize) { + mNewKeyAlgorithmChoice = algorithmChoice; + mNewKeySize = keySize; + createKey(); + } + }); + mCreateKeyDialogFragment.show(mActivity.getSupportFragmentManager(), "createKeyDialog"); + break; + } - default: { - break; - } + default: { + break; + } } this.updateEditorsVisible(); } @@ -220,31 +226,34 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor Bundle data = new Bundle(); Boolean isMasterKey; - String passPhrase; + String passphrase; if (mEditors.getChildCount() > 0) { PGPSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue(); - passPhrase = PassphraseCacheService + passphrase = PassphraseCacheService .getCachedPassphrase(mActivity, masterKey.getKeyID()); isMasterKey = false; } else { - passPhrase = ""; + passphrase = ""; isMasterKey = true; } data.putBoolean(KeychainIntentService.GENERATE_KEY_MASTER_KEY, isMasterKey); - data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passPhrase); + data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase); data.putInt(KeychainIntentService.GENERATE_KEY_ALGORITHM, mNewKeyAlgorithmChoice.getId()); data.putInt(KeychainIntentService.GENERATE_KEY_KEY_SIZE, mNewKeySize); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); // show progress dialog - mGeneratingDialog = ProgressDialogFragment.newInstance(R.string.progress_generating, - ProgressDialog.STYLE_SPINNER, true, new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - mActivity.stopService(intent); - } - }); + mGeneratingDialog = ProgressDialogFragment.newInstance( + getResources().getQuantityString(R.plurals.progress_generating, 1), + ProgressDialog.STYLE_SPINNER, + true, + new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + mActivity.stopService(intent); + } + }); // Message is received after generating is done in ApgService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity, @@ -261,7 +270,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor .getByteArray(KeychainIntentService.RESULT_NEW_KEY)); addGeneratedKeyToView(newKey); } - }; + } }; // Create a new Messenger for the communication back diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java index 5428b626e..71cd89ae8 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.widget; import java.util.regex.Matcher; import java.util.regex.Pattern; +import android.widget.*; import org.sufficientlysecure.keychain.R; import android.content.Context; @@ -26,11 +27,9 @@ import android.util.AttributeSet; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.RadioButton; import com.beardedhen.androidbootstrap.BootstrapButton; +import org.sufficientlysecure.keychain.helper.ContactHelper; public class UserIdEditor extends LinearLayout implements Editor, OnClickListener { private EditorListener mEditorListener = null; @@ -38,7 +37,7 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene private BootstrapButton mDeleteButton; private RadioButton mIsMainUserId; private EditText mName; - private EditText mEmail; + private AutoCompleteTextView mEmail; private EditText mComment; // see http://www.regular-expressions.info/email.html @@ -102,9 +101,17 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene mIsMainUserId.setOnClickListener(this); mName = (EditText) findViewById(R.id.name); - mEmail = (EditText) findViewById(R.id.email); + mEmail = (AutoCompleteTextView) findViewById(R.id.email); mComment = (EditText) findViewById(R.id.comment); + + mEmail.setThreshold(1); // Start working from first character + mEmail.setAdapter( + new ArrayAdapter<String> + (this.getContext(), android.R.layout.simple_dropdown_item_1line, + ContactHelper.getMailAccounts(getContext()) + )); + super.onFinishInflate(); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java index b94c42e90..32266839c 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java @@ -1,6 +1,8 @@ /* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2011 Thialfihar <thi@thialfihar.org> * Copyright (C) 2011 Senecaso - * + * * 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 @@ -224,7 +226,7 @@ public class HkpKeyServer extends KeyServer { HttpClient client = new DefaultHttpClient(); try { HttpGet get = new HttpGet("http://" + mHost + ":" + mPort - + "/pks/lookup?op=get&search=0x" + PgpKeyHelper.convertKeyToHex(keyId)); + + "/pks/lookup?op=get&search=" + PgpKeyHelper.convertKeyIdToHex(keyId)); HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java index 072affb1f..7049820e8 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java @@ -1,6 +1,8 @@ /* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2011 Thialfihar <thi@thialfihar.org> * Copyright (C) 2011 Senecaso - * + * * 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 @@ -16,14 +18,8 @@ package org.sufficientlysecure.keychain.util; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; import java.util.List; -import android.os.Parcel; -import android.os.Parcelable; - import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; public abstract class KeyServer { @@ -52,5 +48,5 @@ public abstract class KeyServer { abstract String get(long keyId) throws QueryException; - abstract void add(String armouredText) throws AddKeyException; + abstract void add(String armoredText) throws AddKeyException; } |