diff options
author | Vincent Breitmoser <valodim@mugenguild.com> | 2015-06-17 19:24:07 +0200 |
---|---|---|
committer | Vincent Breitmoser <valodim@mugenguild.com> | 2015-06-17 19:24:07 +0200 |
commit | 374b21410e82877efcdd1e5110376e975bddbf9f (patch) | |
tree | e17df8931d627e58da677aee274249e1d51c34e2 /OpenKeychain/src/main/java/org/sufficientlysecure | |
parent | 1a7677008bae900fb11a383d04766737aaa3f02f (diff) | |
parent | 04d2b6a5076a1a7264687999152f8c24ece773ab (diff) | |
download | open-keychain-374b21410e82877efcdd1e5110376e975bddbf9f.tar.gz open-keychain-374b21410e82877efcdd1e5110376e975bddbf9f.tar.bz2 open-keychain-374b21410e82877efcdd1e5110376e975bddbf9f.zip |
Merge branch 'v/instrument' into v/multi-decrypt
Conflicts:
.travis.yml
OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure')
24 files changed, 355 insertions, 415 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java index fa3600ffb..2f2838f70 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java @@ -74,6 +74,10 @@ public class ClipboardReflection { Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip"); Object clipData = methodGetPrimaryClip.invoke(clipboard); + if (clipData == null) { + return null; + } + // ClipData.Item clipDataItem = clipData.getItemAt(0); Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", int.class); Object clipDataItem = methodGetItemAt.invoke(clipData, 0); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index 469e386cb..da0aef018 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -82,7 +82,7 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> { CanonicalizedSecretKeyRing secRing = mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId); - modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel, log, 2); + modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel); if (modifyResult.isPending()) { return modifyResult; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 707cf0af1..8c68bda19 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -854,7 +854,11 @@ public abstract class OperationResult implements Parcelable { if (mParcels.isEmpty()) { return null; } - return mParcels.get(mParcels.size() -1); + LogEntryParcel last = mParcels.get(mParcels.size() -1); + if (last instanceof SubLogEntryParcel) { + return ((SubLogEntryParcel) last).getSubResult().getLog().getLast(); + } + return last; } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 4651f12d4..6a85ce251 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -148,7 +148,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> return verifySignedLiteralData(input, aIn, outputStream, 0); } else if (aIn.isClearText()) { // a cleartext signature, verify it with the other method - return verifyCleartextSignature(aIn, 0); + return verifyCleartextSignature(aIn, outputStream, 0); } else { // else: ascii armored encryption! go on... return decryptVerify(input, cryptoInput, in, outputStream, 0); @@ -767,20 +767,11 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> // TODO: slow annealing to fake a progress? } - // after going through the stream, size should be available - Long originalSize = literalData.getDataLengthIfAvailable(); - if (originalSize != null) { - log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, - Long.toString(originalSize)); - } else { - log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1); - } - metadata = new OpenPgpMetadata( originalFilename, mimeType, literalData.getModificationTime().getTime(), - originalSize == null ? 0 : originalSize); + alreadyWritten); if (signature != null) { updateProgress(R.string.progress_verifying_signature, 90, 100); @@ -859,7 +850,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> * pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java */ private DecryptVerifyResult verifyCleartextSignature( - ArmoredInputStream aIn, int indent) throws IOException, PGPException { + ArmoredInputStream aIn, OutputStream outputStream, int indent) throws IOException, PGPException { OperationLog log = new OperationLog(); @@ -889,8 +880,9 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> out.close(); byte[] clearText = out.toByteArray(); - if (out != null) { - out.write(clearText); + if (outputStream != null) { + outputStream.write(clearText); + outputStream.close(); } updateProgress(R.string.progress_processing_signature, 60, 100); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 1da13023d..a018815f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -365,14 +365,9 @@ public class PgpKeyOperation { public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, CryptoInputParcel cryptoInput, SaveKeyringParcel saveParcel) { - return modifySecretKeyRing(wsKR, cryptoInput, saveParcel, new OperationLog(), 0); - } - public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, - CryptoInputParcel cryptoInput, - SaveKeyringParcel saveParcel, - OperationLog log, - int indent) { + OperationLog log = new OperationLog(); + int indent = 0; /* * 1. Unlock private key diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 8253801d9..36ba47672 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -179,7 +179,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { + Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + ")"; - KeychainDatabase(Context context) { + public KeychainDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); mContext = context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 72100677c..dbbfe3133 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -149,6 +149,14 @@ public class PassphraseCacheService extends Service { context.startService(intent); } + public static void clearCachedPassphrases(Context context) { + Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase()"); + + Intent intent = new Intent(context, PassphraseCacheService.class); + intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR); + + context.startService(intent); + } /** * Gets a cached passphrase from memory by sending an intent to the service. This method is diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java index 1dcda5b8d..051da5d6b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java @@ -153,7 +153,9 @@ public class DecryptTextFragment extends DecryptFragment { @Override protected PgpDecryptVerifyInputParcel createOperationInput() { - return new PgpDecryptVerifyInputParcel(mCiphertext.getBytes()); + PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCiphertext.getBytes()); + input.setAllowSymmetricDecryption(true); + return input; } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index ba626cf11..d290c57a6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; @@ -26,8 +25,6 @@ import android.graphics.Point; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -49,10 +46,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpConstants; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; -import org.sufficientlysecure.keychain.service.KeychainNewService; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java index 1a23dda93..355c649e7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -75,7 +75,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false); - mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign); mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); mEncryptKeyView.setThreshold(1); // Start working from first character @@ -168,7 +167,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { @Override public long getAsymmetricSigningKeyId() { - return mSignKeySpinner.getSelectedItemId(); + return mSignKeySpinner.getSelectedKeyId(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java index 83fede917..d93b52453 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java @@ -240,7 +240,7 @@ public class EncryptTextFragment boolean gotEncryptionKeys = (encryptionKeyIds != null && encryptionKeyIds.length > 0); - if (!gotEncryptionKeys && signingKeyId == 0L) { + if (!gotEncryptionKeys && signingKeyId == Constants.key.none) { Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR) .show(this); return null; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index f5a909676..a0f6d0e1b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -51,6 +51,8 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac private static final int ID_SETTINGS = 4; private static final int ID_HELP = 5; + public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time"; + public Drawer.Result mDrawerResult; private Toolbar mToolbar; @@ -114,7 +116,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac // if this is the first time show first time activity Preferences prefs = Preferences.getPreferences(this); - if (prefs.isFirstTime()) { + if (!getIntent().getBooleanExtra(EXTRA_SKIP_FIRST_TIME, false) && prefs.isFirstTime()) { Intent intent = new Intent(this, CreateKeyActivity.class); intent.putExtra(CreateKeyActivity.EXTRA_FIRST_TIME, true); startActivity(intent); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java index 51485cb16..c5fc9abe0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -10,7 +10,6 @@ import android.content.Intent; import android.os.Bundle; import android.view.WindowManager; -import org.spongycastle.util.Arrays; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; @@ -28,6 +27,7 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Arrays; /** * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant @@ -47,6 +47,8 @@ public class NfcOperationActivity extends BaseNfcActivity { private RequiredInputParcel mRequiredInput; private Intent mServiceIntent; + private static final byte[] BLANK_FINGERPRINT = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -126,17 +128,29 @@ public class NfcOperationActivity extends BaseNfcActivity { } if (key.canSign() || key.canCertify()) { - nfcPutKey(0xB6, key, passphrase); - nfcPutData(0xCE, timestampBytes); - nfcPutData(0xC7, key.getFingerprint()); + if (shouldPutKey(key.getFingerprint(), 0)) { + nfcPutKey(0xB6, key, passphrase); + nfcPutData(0xCE, timestampBytes); + nfcPutData(0xC7, key.getFingerprint()); + } else { + throw new IOException("Key slot occupied; card must be reset to put new signature key."); + } } else if (key.canEncrypt()) { - nfcPutKey(0xB8, key, passphrase); - nfcPutData(0xCF, timestampBytes); - nfcPutData(0xC8, key.getFingerprint()); + if (shouldPutKey(key.getFingerprint(), 1)) { + nfcPutKey(0xB8, key, passphrase); + nfcPutData(0xCF, timestampBytes); + nfcPutData(0xC8, key.getFingerprint()); + } else { + throw new IOException("Key slot occupied; card must be reset to put new decryption key."); + } } else if (key.canAuthenticate()) { - nfcPutKey(0xA4, key, passphrase); - nfcPutData(0xD0, timestampBytes); - nfcPutData(0xC9, key.getFingerprint()); + if (shouldPutKey(key.getFingerprint(), 2)) { + nfcPutKey(0xA4, key, passphrase); + nfcPutData(0xD0, timestampBytes); + nfcPutData(0xC9, key.getFingerprint()); + } else { + throw new IOException("Key slot occupied; card must be reset to put new authentication key."); + } } else { throw new IOException("Inappropriate key flags for smart card key."); } @@ -158,6 +172,18 @@ public class NfcOperationActivity extends BaseNfcActivity { finish(); } + private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { + byte[] cardFingerprint = nfcGetFingerprint(idx); + // Slot is empty, or contains this key already. PUT KEY operation is safe + if (Arrays.equals(cardFingerprint, BLANK_FINGERPRINT) || + Arrays.equals(cardFingerprint, fingerprint)) { + return true; + } + + // Slot already contains a different key; don't overwrite it. + return false; + } + @Override public void handlePinError() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index 4b4e71f6e..bb12cd7ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; + import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -43,14 +44,12 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; @@ -75,8 +74,7 @@ public class PassphraseDialogActivity extends FragmentActivity { // special extra for OpenPgpService public static final String EXTRA_SERVICE_INTENT = "data"; - - private static final int REQUEST_CODE_ENTER_PATTERN = 2; + private long mSubKeyId; @Override protected void onCreate(Bundle savedInstanceState) { @@ -93,14 +91,13 @@ public class PassphraseDialogActivity extends FragmentActivity { // this activity itself has no content view (see manifest) - long keyId; if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) { - keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0); + mSubKeyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0); } else { RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT); switch (requiredInput.mType) { case PASSPHRASE_SYMMETRIC: { - keyId = Constants.key.symmetric; + mSubKeyId = Constants.key.symmetric; break; } case PASSPHRASE: { @@ -127,7 +124,7 @@ public class PassphraseDialogActivity extends FragmentActivity { return; } - keyId = requiredInput.getSubKeyId(); + mSubKeyId = requiredInput.getSubKeyId(); break; } default: { @@ -136,62 +133,35 @@ public class PassphraseDialogActivity extends FragmentActivity { } } - Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT); - - show(this, keyId, serviceIntent); } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_ENTER_PATTERN: { - /* - * NOTE that there are 4 possible result codes!!! - */ - switch (resultCode) { - case RESULT_OK: - // The user passed - break; - case RESULT_CANCELED: - // The user cancelled the task - break; -// case LockPatternActivity.RESULT_FAILED: -// // The user failed to enter the pattern -// break; -// case LockPatternActivity.RESULT_FORGOT_PATTERN: -// // The user forgot the pattern and invoked your recovery Activity. -// break; - } + protected void onResume() { + super.onResume(); - /* - * In any case, there's always a key EXTRA_RETRY_COUNT, which holds - * the number of tries that the user did. - */ -// int retryCount = data.getIntExtra( -// LockPatternActivity.EXTRA_RETRY_COUNT, 0); + /* Show passphrase dialog to cache a new passphrase the user enters for using it later for + * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks + * for a symmetric passphrase + */ - break; - } - } + Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT); + + PassphraseDialogFragment frag = new PassphraseDialogFragment(); + Bundle args = new Bundle(); + args.putLong(EXTRA_SUBKEY_ID, mSubKeyId); + args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent); + frag.setArguments(args); + frag.show(getSupportFragmentManager(), "passphraseDialog"); } - /** - * Shows passphrase dialog to cache a new passphrase the user enters for using it later for - * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks - * for a symmetric passphrase - */ - public static void show(final FragmentActivity context, final long keyId, final Intent serviceIntent) { - DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { - public void run() { - // do NOT check if the key even needs a passphrase. that's not our job here. - PassphraseDialogFragment frag = new PassphraseDialogFragment(); - Bundle args = new Bundle(); - args.putLong(EXTRA_SUBKEY_ID, keyId); - args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent); - frag.setArguments(args); - frag.show(context.getSupportFragmentManager(), "passphraseDialog"); - } - }); + @Override + protected void onPause() { + super.onPause(); + + DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag("passphraseDialog"); + if (dialog != null) { + dialog.dismiss(); + } } public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener { @@ -205,9 +175,6 @@ public class PassphraseDialogActivity extends FragmentActivity { private Intent mServiceIntent; - /** - * Creates dialog - */ @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); @@ -268,12 +235,7 @@ public class PassphraseDialogActivity extends FragmentActivity { userId = null; } - /* Get key type for message */ - // find a master key id for our key - long masterKeyId = new ProviderHelper(activity).getMasterKeyId(mSubKeyId); - CachedPublicKeyRing keyRing = new ProviderHelper(activity).getCachedPublicKeyRing(masterKeyId); - // get the type of key (from the database) - keyType = keyRing.getSecretKeyType(mSubKeyId); + keyType = mSecretRing.getSecretKey(mSubKeyId).getSecretKeyType(); switch (keyType) { case PASSPHRASE: message = getString(R.string.passphrase_for, userId); @@ -468,20 +430,16 @@ public class PassphraseDialogActivity extends FragmentActivity { // note we need no synchronization here, this variable is only accessed in the ui thread mIsCancelled = true; + + getActivity().setResult(RESULT_CANCELED); + getActivity().finish(); } @Override public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); - if (getActivity() == null) { - return; - } - hideKeyboard(); - - getActivity().setResult(RESULT_CANCELED); - getActivity().finish(); } private void hideKeyboard() { @@ -495,11 +453,9 @@ public class PassphraseDialogActivity extends FragmentActivity { inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); } - /** - * Associate the "done" button on the soft keyboard with the okay button in the view - */ @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + // Associate the "done" button on the soft keyboard with the okay button in the view if (EditorInfo.IME_ACTION_DONE == actionId) { AlertDialog dialog = ((AlertDialog) getDialog()); Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java index b92163b59..a929d52f0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -300,7 +300,7 @@ public class ViewKeyFragment extends LoaderFragment implements * because the notification triggers faster than the activity closes. */ // Avoid NullPointerExceptions... - if (data.getCount() == 0) { + if (data == null || data.getCount() == 0) { return; } // Swap the new cursor in. (The framework will take care of closing the diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index deaaee87a..1fc24775b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -19,15 +19,16 @@ package org.sufficientlysecure.keychain.ui.adapter; import java.io.Serializable; -import java.util.Calendar; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Date; -import java.util.TimeZone; +import java.util.List; import android.content.Context; import android.database.Cursor; import android.graphics.PorterDuff; import android.support.v4.widget.CursorAdapter; -import android.text.format.DateFormat; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; @@ -60,7 +61,6 @@ public class KeyAdapter extends CursorAdapter { KeyRings.VERIFIED, KeyRings.HAS_ANY_SECRET, KeyRings.HAS_DUPLICATE_USER_ID, - KeyRings.HAS_ENCRYPT, KeyRings.FINGERPRINT, KeyRings.CREATION, }; @@ -72,9 +72,8 @@ public class KeyAdapter extends CursorAdapter { public static final int INDEX_VERIFIED = 5; public static final int INDEX_HAS_ANY_SECRET = 6; public static final int INDEX_HAS_DUPLICATE_USER_ID = 7; - public static final int INDEX_HAS_ENCRYPT = 8; - public static final int INDEX_FINGERPRINT = 9; - public static final int INDEX_CREATION = 10; + public static final int INDEX_FINGERPRINT = 8; + public static final int INDEX_CREATION = 9; public KeyAdapter(Context context, Cursor c, int flags) { super(context, c, flags); @@ -87,6 +86,7 @@ public class KeyAdapter extends CursorAdapter { } public static class KeyItemViewHolder { + public View mView; public Long mMasterKeyId; public TextView mMainUserId; public TextView mMainUserIdRest; @@ -96,6 +96,7 @@ public class KeyAdapter extends CursorAdapter { public ImageButton mSlingerButton; public KeyItemViewHolder(View view) { + mView = view; mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name); mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email); mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon); @@ -104,11 +105,10 @@ public class KeyAdapter extends CursorAdapter { mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation); } - public void setData(Context context, Cursor cursor, Highlighter highlighter) { + public void setData(Context context, KeyItem item, Highlighter highlighter) { { // set name and stuff, common to both key types - String userId = cursor.getString(INDEX_USER_ID); - KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId); + KeyRing.UserId userIdSplit = item.mUserId; if (userIdSplit.name != null) { mMainUserId.setText(highlighter.highlight(userIdSplit.name)); } else { @@ -124,30 +124,23 @@ public class KeyAdapter extends CursorAdapter { { // set edit button and status, specific by key type - long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); - boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0; - boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; - boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; - boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0; - boolean hasDuplicate = cursor.getInt(INDEX_HAS_DUPLICATE_USER_ID) != 0; - - mMasterKeyId = masterKeyId; + mMasterKeyId = item.mKeyId; // Note: order is important! - if (isRevoked) { + if (item.mIsRevoked) { KeyFormattingUtils .setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray); mStatus.setVisibility(View.VISIBLE); mSlinger.setVisibility(View.GONE); mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray)); - } else if (isExpired) { + } else if (item.mIsExpired) { KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray); mStatus.setVisibility(View.VISIBLE); mSlinger.setVisibility(View.GONE); mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray)); - } else if (isSecret) { + } else if (item.mIsSecret) { mStatus.setVisibility(View.GONE); if (mSlingerButton.hasOnClickListeners()) { mSlinger.setVisibility(View.VISIBLE); @@ -158,7 +151,7 @@ public class KeyAdapter extends CursorAdapter { mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black)); } else { // this is a public key - show if it's verified - if (isVerified) { + if (item.mIsVerified) { KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED); mStatus.setVisibility(View.VISIBLE); } else { @@ -170,9 +163,9 @@ public class KeyAdapter extends CursorAdapter { mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black)); } - if (hasDuplicate) { + if (item.mHasDuplicate) { String dateTime = DateUtils.formatDateTime(context, - cursor.getLong(INDEX_CREATION) * 1000, + item.mCreation.getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); @@ -190,6 +183,10 @@ public class KeyAdapter extends CursorAdapter { } + public boolean isEnabled(Cursor cursor) { + return true; + } + @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { View view = mInflater.inflate(R.layout.key_list_item, parent, false); @@ -204,7 +201,8 @@ public class KeyAdapter extends CursorAdapter { public void bindView(View view, Context context, Cursor cursor) { Highlighter highlighter = new Highlighter(context, mQuery); KeyItemViewHolder h = (KeyItemViewHolder) view.getTag(); - h.setData(context, cursor, highlighter); + KeyItem item = new KeyItem(cursor); + h.setData(context, item, highlighter); } public boolean isSecretAvailable(int id) { @@ -234,8 +232,9 @@ public class KeyAdapter extends CursorAdapter { @Override public long getItemId(int position) { + Cursor cursor = getCursor(); // prevent a crash on rapid cursor changes - if (getCursor().isClosed()) { + if (cursor != null && getCursor().isClosed()) { return 0L; } return super.getItemId(position); @@ -250,6 +249,7 @@ public class KeyAdapter extends CursorAdapter { public final boolean mHasDuplicate; public final Date mCreation; public final String mFingerprint; + public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified; private KeyItem(Cursor cursor) { String userId = cursor.getString(INDEX_USER_ID); @@ -260,6 +260,10 @@ public class KeyAdapter extends CursorAdapter { mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000); mFingerprint = KeyFormattingUtils.convertFingerprintToHex( cursor.getBlob(INDEX_FINGERPRINT)); + mIsSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0; + mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; + mIsExpired = cursor.getInt(INDEX_IS_EXPIRED) > 0; + mIsVerified = cursor.getInt(INDEX_VERIFIED) > 0; } public KeyItem(CanonicalizedPublicKeyRing ring) { @@ -272,6 +276,12 @@ public class KeyAdapter extends CursorAdapter { mCreation = key.getCreationTime(); mFingerprint = KeyFormattingUtils.convertFingerprintToHex( ring.getFingerprint()); + mIsRevoked = key.isRevoked(); + mIsExpired = key.isExpired(); + + // these two are actually "don't know"s + mIsSecret = false; + mIsVerified = false; } public String getReadableName() { @@ -284,4 +294,11 @@ public class KeyAdapter extends CursorAdapter { } + public static String[] getProjectionWith(String[] projection) { + List<String> list = new ArrayList<>(); + list.addAll(Arrays.asList(PROJECTION)); + list.addAll(Arrays.asList(projection)); + return list.toArray(new String[list.size()]); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java index 3445107a6..a28a5ea59 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java @@ -95,6 +95,8 @@ public abstract class BaseNfcActivity extends BaseActivity { if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { try { handleNdefDiscoveredIntent(intent); + } catch (CardException e) { + handleNfcError(e); } catch (IOException e) { handleNfcError(e); } @@ -105,6 +107,81 @@ public abstract class BaseNfcActivity extends BaseActivity { Log.e(Constants.TAG, "nfc error", e); Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show(); + } + + public void handleNfcError(CardException e) { + Log.e(Constants.TAG, "card error", e); + + short status = e.getResponseCode(); + // When entering a PIN, a status of 63CX indicates X attempts remaining. + if ((status & (short)0xFFF0) == 0x63C0) { + Notify.create(this, getString(R.string.error_pin, status & 0x000F), Style.WARN).show(); + return; + } + + // Otherwise, all status codes are fixed values. + switch (status) { + // These errors should not occur in everyday use; if they are returned, it means we + // made a mistake sending data to the card, or the card is misbehaving. + case 0x6A80: { + Notify.create(this, getString(R.string.error_nfc_bad_data), Style.WARN).show(); + break; + } + case 0x6883: { + Notify.create(this, getString(R.string.error_nfc_chaining_error), Style.WARN).show(); + break; + } + case 0x6B00: { + Notify.create(this, getString(R.string.error_nfc_header, "P1/P2"), Style.WARN).show(); + break; + } + case 0x6D00: { + Notify.create(this, getString(R.string.error_nfc_header, "INS"), Style.WARN).show(); + break; + } + case 0x6E00: { + Notify.create(this, getString(R.string.error_nfc_header, "CLA"), Style.WARN).show(); + break; + } + // These error conditions are more likely to be experienced by an end user. + case 0x6285: { + Notify.create(this, getString(R.string.error_nfc_terminated), Style.WARN).show(); + break; + } + case 0x6700: { + Notify.create(this, getString(R.string.error_nfc_wrong_length), Style.WARN).show(); + break; + } + case 0x6982: { + Notify.create(this, getString(R.string.error_nfc_security_not_satisfied), + Style.WARN).show(); + break; + } + case 0x6983: { + Notify.create(this, getString(R.string.error_nfc_authentication_blocked), + Style.WARN).show(); + break; + } + case 0x6985: { + Notify.create(this, getString(R.string.error_nfc_conditions_not_satisfied), + Style.WARN).show(); + break; + } + // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. + case 0x6A88: + case 0x6A83: { + Notify.create(this, getString(R.string.error_nfc_data_not_found), Style.WARN).show(); + break; + } + // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an + // unhandled exception on the smart card. + case 0x6F00: { + Notify.create(this, getString(R.string.error_nfc_unknown), Style.WARN).show(); + break; + } + default: + Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show(); + } } @@ -223,8 +300,9 @@ public abstract class BaseNfcActivity extends BaseActivity { + "06" // Lc (number of bytes) + "D27600012401" // Data (6 bytes) + "00"; // Le - if ( ! nfcCommunicate(opening).endsWith(accepted)) { // activate connection - throw new IOException("Initialization failed!"); + String response = nfcCommunicate(opening); // activate connection + if ( ! response.endsWith(accepted) ) { + throw new CardException("Initialization failed!", parseCardStatus(response)); } byte[] pwStatusBytes = nfcGetPwStatusBytes(); @@ -439,7 +517,7 @@ public abstract class BaseNfcActivity extends BaseActivity { } if ( ! "9000".equals(status)) { - throw new IOException("Bad NFC response code: " + status); + throw new CardException("Bad NFC response code: " + status, parseCardStatus(response)); } // Make sure the signature we received is actually the expected number of bytes long! @@ -511,9 +589,10 @@ public abstract class BaseNfcActivity extends BaseActivity { + String.format("%02x", mode) // P2 + String.format("%02x", pin.length) // Lc + Hex.toHexString(pin); - if (!nfcCommunicate(login).equals(accepted)) { // login + String response = nfcCommunicate(login); // login + if (!response.equals(accepted)) { handlePinError(); - throw new IOException("Bad PIN!"); + throw new CardException("Bad PIN!", parseCardStatus(response)); } if (mode == 0x81) { @@ -567,9 +646,10 @@ public abstract class BaseNfcActivity extends BaseActivity { + String.format("%02x", pin.length + newPin.length) // Lc + getHex(pin) + getHex(newPin); - if (!nfcCommunicate(changeReferenceDataApdu).equals("9000")) { // Change reference data + String response = nfcCommunicate(changeReferenceDataApdu); // change PIN + if (!response.equals("9000")) { handlePinError(); - throw new IOException("Failed to change PIN"); + throw new CardException("Failed to change PIN", parseCardStatus(response)); } } @@ -600,12 +680,9 @@ public abstract class BaseNfcActivity extends BaseActivity { + String.format("%02x", data.length) // Lc + getHex(data); - String response = nfcCommunicate(putDataApdu); + String response = nfcCommunicate(putDataApdu); // put data if (!response.equals("9000")) { - throw new IOException("Failed to put data for tag " - + String.format("%02x", (dataObject & 0xFF00) >> 8) - + String.format("%02x", dataObject & 0xFF) - + ": " + response); + throw new CardException("Failed to put data.", parseCardStatus(response)); } } @@ -713,7 +790,7 @@ public abstract class BaseNfcActivity extends BaseActivity { } if (!response.endsWith("9000")) { - throw new IOException("Key export to card failed"); + throw new CardException("Key export to card failed", parseCardStatus(response)); } } @@ -722,6 +799,24 @@ public abstract class BaseNfcActivity extends BaseActivity { } /** + * Parses out the status word from a JavaCard response string. + * + * @param response A hex string with the response from the card + * @return A short indicating the SW1/SW2, or 0 if a status could not be determined. + */ + short parseCardStatus(String response) { + if (response.length() < 4) { + return 0; // invalid input + } + + try { + return Short.parseShort(response.substring(response.length() - 4), 16); + } catch (NumberFormatException e) { + return 0; + } + } + + /** * Prints a message to the screen * * @param text the text which should be contained within the toast @@ -790,4 +885,18 @@ public abstract class BaseNfcActivity extends BaseActivity { return new String(Hex.encode(raw)); } + public class CardException extends IOException { + private short mResponseCode; + + public CardException(String detailMessage, short responseCode) { + super(detailMessage); + mResponseCode = responseCode; + } + + public short getResponseCode() { + return mResponseCode; + } + + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java index 8ed4cbc87..87c6ee3ca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java @@ -1,19 +1,10 @@ package org.sufficientlysecure.keychain.ui.base; -import android.app.ProgressDialog; -import android.content.Intent; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.os.Parcelable; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.service.KeychainNewService; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; public abstract class CachingCryptoOperationFragment <T extends Parcelable, S extends OperationResult> @@ -47,59 +38,6 @@ public abstract class CachingCryptoOperationFragment <T extends Parcelable, S ex protected abstract T createOperationInput(); - protected void cryptoOperation(CryptoInputParcel cryptoInput) { - - if (mCachedActionsParcel == null) { - - mCachedActionsParcel = createOperationInput(); - // this is null if invalid, just return in that case - if (mCachedActionsParcel == null) { - // Notify was created by createCryptoInput. - return; - } - - } - - // Send all information needed to service to edit key in other thread - Intent intent = new Intent(getActivity(), KeychainNewService.class); - - intent.putExtra(KeychainNewService.EXTRA_OPERATION_INPUT, mCachedActionsParcel); - intent.putExtra(KeychainNewService.EXTRA_CRYPTO_INPUT, cryptoInput); - - ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - - // get returned data bundle - Bundle returnData = message.getData(); - if (returnData == null) { - return; - } - - final OperationResult result = - returnData.getParcelable(OperationResult.EXTRA_RESULT); - - onHandleResult(result); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - saveHandler.showProgressDialog( - getString(R.string.progress_building_key), - ProgressDialog.STYLE_HORIZONTAL, false); - - getActivity().startService(intent); - - } - protected T getCachedActionsParcel() { return mCachedActionsParcel; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java index 0bba2f964..5f1097588 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java @@ -192,7 +192,7 @@ public abstract class CryptoOperationFragment <T extends Parcelable, S extends O abstract protected void onCryptoOperationSuccess(S result); protected void onCryptoOperationError(S result) { - result.createNotify(getActivity()).show(this); + result.createNotify(getActivity()).show(); } protected void onCryptoOperationCancelled() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java index 8c554dbde..7dfd56430 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java @@ -38,25 +38,16 @@ import org.sufficientlysecure.keychain.util.FabContainer; public class Notify { public static enum Style { - OK, WARN, ERROR; + OK (R.color.android_green_light), WARN(R.color.android_orange_light), ERROR(R.color.android_red_light); - public void applyToBar(Snackbar bar) { + public final int mLineColor; - switch (this) { - case OK: - // bar.actionColorResource(R.color.android_green_light); - bar.lineColorResource(R.color.android_green_light); - break; - case WARN: - // bar.textColorResource(R.color.android_orange_light); - bar.lineColorResource(R.color.android_orange_light); - break; - case ERROR: - // bar.textColorResource(R.color.android_red_light); - bar.lineColorResource(R.color.android_red_light); - break; - } + Style(int color) { + mLineColor = color; + } + public void applyToBar(Snackbar bar) { + bar.lineColorResource(mLineColor); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java index 845b35512..0fed46544 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -17,6 +17,10 @@ package org.sufficientlysecure.keychain.ui.widget; + +import java.util.Arrays; +import java.util.List; + import android.content.Context; import android.database.Cursor; import android.net.Uri; @@ -24,14 +28,11 @@ import android.os.Bundle; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.AttributeSet; -import android.widget.ImageView; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; +import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; public class CertifyKeySpinner extends KeySpinner { private long mHiddenMasterKeyId = Constants.key.none; @@ -59,19 +60,9 @@ public class CertifyKeySpinner extends KeySpinner { // sample only has one Loader, so we don't care about the ID. Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri(); - // These are the rows that we will retrieve. - String[] projection = new String[]{ - KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.KEY_ID, - KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.IS_EXPIRED, + String[] projection = KeyAdapter.getProjectionWith(new String[] { KeychainContract.KeyRings.HAS_CERTIFY, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID, - KeychainContract.KeyRings.CREATION - }; + }); String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND " + KeychainDatabase.Tables.KEYS + "." + KeychainContract.KeyRings.MASTER_KEY_ID @@ -82,7 +73,7 @@ public class CertifyKeySpinner extends KeySpinner { return new CursorLoader(getContext(), baseUri, projection, where, null, null); } - private int mIndexHasCertify, mIndexIsRevoked, mIndexIsExpired; + private int mIndexHasCertify; @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { @@ -90,8 +81,6 @@ public class CertifyKeySpinner extends KeySpinner { if (loader.getId() == LOADER_ID) { mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY); - mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED); - mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED); // If: // - no key has been pre-selected (e.g. by SageSlinger) @@ -119,18 +108,15 @@ public class CertifyKeySpinner extends KeySpinner { @Override - boolean setStatus(Context context, Cursor cursor, ImageView statusView) { - if (cursor.getInt(mIndexIsRevoked) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray); + boolean isItemEnabled(Cursor cursor) { + if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { return false; } - if (cursor.getInt(mIndexIsExpired) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray); + if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) { return false; } // don't invalidate the "None" entry, which is also null! if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray); return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index 63a1aade9..48e6c2cee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -38,6 +38,7 @@ import com.tokenautocomplete.TokenCompleteTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; @@ -126,7 +127,13 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView public Loader<Cursor> onCreateLoader(int id, Bundle args) { // These are the rows that we will retrieve. Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); - String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND " + KeyRings.IS_EXPIRED + " = 0 AND " + + String[] projection = KeyAdapter.getProjectionWith(new String[] { + KeychainContract.KeyRings.HAS_ENCRYPT, + }); + + String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND " + + KeyRings.IS_EXPIRED + " = 0 AND " + Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0"; if (args != null && args.containsKey(ARG_QUERY)) { @@ -135,12 +142,12 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView where += " AND " + KeyRings.USER_ID + " LIKE ?"; - return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, + return new CursorLoader(getContext(), baseUri, projection, where, new String[]{"%" + query + "%"}, null); } mAdapter.setSearchQuery(null); - return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, null, null); + return new CursorLoader(getContext(), baseUri, projection, where, null, null); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index ad1a14a33..5050c01af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -17,33 +17,30 @@ package org.sufficientlysecure.keychain.ui.widget; -import android.annotation.SuppressLint; + import android.content.Context; import android.database.Cursor; -import android.graphics.Color; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; -import android.support.v4.widget.CursorAdapter; import android.support.v7.widget.AppCompatSpinner; -import android.text.format.DateUtils; import android.util.AttributeSet; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; -import android.widget.ImageView; import android.widget.SpinnerAdapter; -import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; +import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; + /** * Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon. @@ -119,7 +116,8 @@ public abstract class KeySpinner extends AppCompatSpinner implements if (getContext() instanceof FragmentActivity) { ((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this); } else { - Log.e(Constants.TAG, "KeySpinner must be attached to FragmentActivity, this is " + getContext().getClass()); + throw new AssertionError("KeySpinner must be attached to FragmentActivity, this is " + + getContext().getClass()); } } @@ -138,7 +136,11 @@ public abstract class KeySpinner extends AppCompatSpinner implements } public long getSelectedKeyId() { - return getSelectedItemId(); + Object item = getSelectedItem(); + if (item instanceof KeyItem) { + return ((KeyItem) item).mKeyId; + } + return Constants.key.none; } public void setPreSelectedKeyId(long selectedKeyId) { @@ -146,161 +148,87 @@ public abstract class KeySpinner extends AppCompatSpinner implements } protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter { - private CursorAdapter inner; - private int mIndexUserId; - private int mIndexDuplicate; + private KeyAdapter inner; private int mIndexMasterKeyId; - private int mIndexCreationDate; public SelectKeyAdapter() { - inner = new CursorAdapter(getContext(), null, 0) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return View.inflate(getContext(), R.layout.keyspinner_item, null); - } + inner = new KeyAdapter(getContext(), null, 0) { @Override - public void bindView(View view, Context context, Cursor cursor) { - TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name); - ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status); - TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); - TextView vDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate); - - KeyRing.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexUserId)); - vKeyName.setText(userId.name); - vKeyEmail.setText(userId.email); - - boolean duplicate = cursor.getLong(mIndexDuplicate) > 0; - if (duplicate) { - String dateTime = DateUtils.formatDateTime(context, - cursor.getLong(mIndexCreationDate) * 1000, - DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_ABBREV_MONTH); - - vDuplicate.setText(context.getString(R.string.label_key_created, dateTime)); - vDuplicate.setVisibility(View.VISIBLE); - } else { - vDuplicate.setVisibility(View.GONE); - } - - boolean valid = setStatus(getContext(), cursor, vKeyStatus); - setItemEnabled(view, valid); + public boolean isEnabled(Cursor cursor) { + return KeySpinner.this.isItemEnabled(cursor); } - @Override - public long getItemId(int position) { - try { - return ((Cursor) getItem(position)).getLong(mIndexMasterKeyId); - } catch (Exception e) { - // This can happen on concurrent modification :( - return Constants.key.none; - } - } }; } - private void setItemEnabled(View view, boolean enabled) { - TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name); - ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status); - TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); - TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate); - - if (enabled) { - vKeyName.setTextColor(Color.BLACK); - vKeyEmail.setTextColor(Color.BLACK); - vKeyDuplicate.setTextColor(Color.BLACK); - vKeyStatus.setVisibility(View.GONE); - view.setClickable(false); - } else { - vKeyName.setTextColor(Color.GRAY); - vKeyEmail.setTextColor(Color.GRAY); - vKeyDuplicate.setTextColor(Color.GRAY); - vKeyStatus.setVisibility(View.VISIBLE); - // this is a HACK. the trick is, if the element itself is clickable, the - // click is not passed on to the view list - view.setClickable(true); - } - } - public Cursor swapCursor(Cursor newCursor) { if (newCursor == null) return inner.swapCursor(null); - mIndexDuplicate = newCursor.getColumnIndex(KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID); - mIndexUserId = newCursor.getColumnIndex(KeychainContract.KeyRings.USER_ID); mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID); - mIndexCreationDate = newCursor.getColumnIndex(KeychainContract.KeyRings.CREATION); + + Cursor oldCursor = inner.swapCursor(newCursor); // pre-select key if mPreSelectedKeyId is given if (mPreSelectedKeyId != Constants.key.none && newCursor.moveToFirst()) { do { if (newCursor.getLong(mIndexMasterKeyId) == mPreSelectedKeyId) { - setSelection(newCursor.getPosition() + 1); + setSelection(newCursor.getPosition() +1); } } while (newCursor.moveToNext()); } - return inner.swapCursor(newCursor); + return oldCursor; } @Override public int getCount() { - return inner.getCount() + 1; + return inner.getCount() +1; } @Override public Object getItem(int position) { - if (position == 0) return null; - return inner.getItem(position - 1); + if (position == 0) { + return null; + } + return inner.getItem(position -1); } @Override public long getItemId(int position) { - if (position == 0) return Constants.key.none; - return inner.getItemId(position - 1); + if (position == 0) { + return Constants.key.none; + } + return inner.getItemId(position -1); } - @SuppressLint("ViewHolder") // inflate call is for the preview only @Override public View getView(int position, View convertView, ViewGroup parent) { - try { - View v = getDropDownView(position, convertView, parent); - v.findViewById(R.id.keyspinner_key_email).setVisibility(View.GONE); - return v; - } catch (NullPointerException e) { - // This is for the preview... - return View.inflate(getContext(), android.R.layout.simple_list_item_1, null); - } - } - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { - View view; - if (position == 0) { - if (convertView == null) { - view = inner.newView(null, null, parent); - } else { - view = convertView; + // Unfortunately, SpinnerAdapter does not support multiple view + // types. For this reason, we throw away convertViews of a bad + // type. This is sort of a hack, but since the number of elements + // we deal with in KeySpinners is usually very small (number of + // secret keys), this is the easiest solution. (I'm sorry.) + if (convertView != null) { + // This assumes that the inner view has non-null tags on its views! + boolean isWrongType = (convertView.getTag() == null) != (position == 0); + if (isWrongType) { + convertView = null; } - TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name); - ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status); - TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); - TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate); - - vKeyName.setText(R.string.choice_none); - vKeyEmail.setVisibility(View.GONE); - vKeyDuplicate.setVisibility(View.GONE); - vKeyStatus.setVisibility(View.GONE); - setItemEnabled(view, true); - } else { - view = inner.getView(position - 1, convertView, parent); - TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); - vKeyEmail.setVisibility(View.VISIBLE); } - return view; + + if (position > 0) { + return inner.getView(position -1, convertView, parent); + } + + return convertView != null ? convertView : + LayoutInflater.from(getContext()).inflate( + R.layout.keyspinner_item_none, parent, false); } + } - boolean setStatus(Context context, Cursor cursor, ImageView statusView) { + boolean isItemEnabled(Cursor cursor) { return true; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java index df7347fa4..c59ad7a12 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui.widget; + import android.content.Context; import android.database.Cursor; import android.net.Uri; @@ -24,12 +25,9 @@ import android.os.Bundle; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.AttributeSet; -import android.widget.ImageView; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; +import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; public class SignKeySpinner extends KeySpinner { public SignKeySpinner(Context context) { @@ -50,19 +48,9 @@ public class SignKeySpinner extends KeySpinner { // sample only has one Loader, so we don't care about the ID. Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri(); - // These are the rows that we will retrieve. - String[] projection = new String[]{ - KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.KEY_ID, - KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.IS_EXPIRED, + String[] projection = KeyAdapter.getProjectionWith(new String[] { KeychainContract.KeyRings.HAS_SIGN, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID, - KeychainContract.KeyRings.CREATION - }; + }); String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1"; @@ -71,7 +59,7 @@ public class SignKeySpinner extends KeySpinner { return new CursorLoader(getContext(), baseUri, projection, where, null, null); } - private int mIndexHasSign, mIndexIsRevoked, mIndexIsExpired; + private int mIndexHasSign; @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { @@ -79,23 +67,18 @@ public class SignKeySpinner extends KeySpinner { if (loader.getId() == LOADER_ID) { mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN); - mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED); - mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED); } } @Override - boolean setStatus(Context context, Cursor cursor, ImageView statusView) { - if (cursor.getInt(mIndexIsRevoked) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray); + boolean isItemEnabled(Cursor cursor) { + if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { return false; } - if (cursor.getInt(mIndexIsExpired) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray); + if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) { return false; } if (cursor.getInt(mIndexHasSign) == 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray); return false; } |