From 14226461c14472fd470a42357b5be5bd7bc134c7 Mon Sep 17 00:00:00 2001 From: Joey Castillo Date: Wed, 10 Jun 2015 17:13:14 -0400 Subject: Improved smart card error handling --- .../keychain/ui/NfcOperationActivity.java | 46 +++++-- .../keychain/ui/base/BaseNfcActivity.java | 137 +++++++++++++++++++-- OpenKeychain/src/main/res/values/strings.xml | 8 ++ 3 files changed, 168 insertions(+), 23 deletions(-) (limited to 'OpenKeychain') 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/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java index 3445107a6..3e9ed282e 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,83 @@ 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. + case 0x6A80: + throw new AssertionError("Card returned 'Wrong Data' status; this is a programming error!"); + case 0x6883: + throw new AssertionError("Card expected last command in a chain; this is a programming error!"); + case 0x6B00: + throw new AssertionError("Card reported invalid P1/P2 parameter; this is a programming error!"); + case 0x6D00: + throw new AssertionError("Instruction (INS) not supported by smart card; this is a programming error!"); + case 0x6E00: + throw new AssertionError("Class (CLA) not supported by smart card; this is a programming error!"); + + // These errors might be encountered in everyday use, and should display a localized + // error message to the user. + case 0x6285: + { + Notify.create(this, getString(R.string.error_nfc, + getString(R.string.error_nfc_terminated)), Style.WARN).show(); + break; + } + case 0x6700: + { + Notify.create(this, getString(R.string.error_nfc, + getString(R.string.error_nfc_wrong_length)), Style.WARN).show(); + break; + } + case 0x6982: + { + Notify.create(this, getString(R.string.error_nfc, + getString(R.string.error_nfc_security_not_satisfied)), Style.WARN).show(); + break; + } + case 0x6983: + { + Notify.create(this, getString(R.string.error_nfc, + getString(R.string.error_nfc_authentication_blocked)), Style.WARN).show(); + break; + } + case 0x6985: + { + Notify.create(this, getString(R.string.error_nfc, + getString(R.string.error_nfc_conditions_not_satisfied)), Style.WARN).show(); + break; + } + case 0x6A88: + case 0x6A83: + { + Notify.create(this, getString(R.string.error_nfc, + 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, + 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 +302,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 +519,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 +591,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 +648,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 +682,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 +792,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)); } } @@ -721,6 +800,24 @@ public abstract class BaseNfcActivity extends BaseActivity { Arrays.fill(dataToSend, (byte) 0); } + /** + * 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 * @@ -790,4 +887,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/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 7323d19cd..8e2f0f5e2 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1309,6 +1309,14 @@ "Import" Different key stored on YubiKey! "NFC Error: %s" + "Incorrect PIN; %d tries remaining." + "Smart card in termination state" + "Wrong length for sent / received data" + "Conditions of use not satisfied" + "Security status not satisfied" + "PIN blocked after too many attempts" + "Key or object not found" + "Unknown Error" Default PIN was rejected! Error creating temporary file. Bluetooth sharing will fail. "Filenames are encrypted." -- cgit v1.2.3 From 5d652e4c418b42eef0517d1110b782908bfd8235 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 11 Jun 2015 17:10:40 +0200 Subject: some bugfixes for new CryptoOperationFragment --- .../keychain/ui/EncryptFilesFragment.java | 7 --- .../ui/base/CachingCryptoOperationFragment.java | 62 ---------------------- .../keychain/ui/base/CryptoOperationFragment.java | 2 +- 3 files changed, 1 insertion(+), 70 deletions(-) (limited to 'OpenKeychain') 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/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 @@ -47,59 +38,6 @@ public abstract class CachingCryptoOperationFragment Date: Thu, 11 Jun 2015 11:52:09 -0400 Subject: Replace AssertionErrors with snackbar notifications, fix style issues. --- .../keychain/ui/base/BaseNfcActivity.java | 82 +++++++++++----------- OpenKeychain/src/main/res/values/strings.xml | 19 ++--- 2 files changed, 51 insertions(+), 50 deletions(-) (limited to 'OpenKeychain') 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 3e9ed282e..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 @@ -122,63 +122,61 @@ public abstract class BaseNfcActivity extends BaseActivity { // 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. - case 0x6A80: - throw new AssertionError("Card returned 'Wrong Data' status; this is a programming error!"); - case 0x6883: - throw new AssertionError("Card expected last command in a chain; this is a programming error!"); - case 0x6B00: - throw new AssertionError("Card reported invalid P1/P2 parameter; this is a programming error!"); - case 0x6D00: - throw new AssertionError("Instruction (INS) not supported by smart card; this is a programming error!"); - case 0x6E00: - throw new AssertionError("Class (CLA) not supported by smart card; this is a programming error!"); - - // These errors might be encountered in everyday use, and should display a localized - // error message to the user. - case 0x6285: - { - Notify.create(this, getString(R.string.error_nfc, - getString(R.string.error_nfc_terminated)), Style.WARN).show(); + // 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 0x6700: - { - Notify.create(this, getString(R.string.error_nfc, - getString(R.string.error_nfc_wrong_length)), Style.WARN).show(); + case 0x6883: { + Notify.create(this, getString(R.string.error_nfc_chaining_error), Style.WARN).show(); break; } - case 0x6982: - { - Notify.create(this, getString(R.string.error_nfc, - getString(R.string.error_nfc_security_not_satisfied)), Style.WARN).show(); + case 0x6B00: { + Notify.create(this, getString(R.string.error_nfc_header, "P1/P2"), Style.WARN).show(); break; } - case 0x6983: - { - Notify.create(this, getString(R.string.error_nfc, - getString(R.string.error_nfc_authentication_blocked)), Style.WARN).show(); + case 0x6D00: { + Notify.create(this, getString(R.string.error_nfc_header, "INS"), Style.WARN).show(); break; } - case 0x6985: - { - Notify.create(this, getString(R.string.error_nfc, - getString(R.string.error_nfc_conditions_not_satisfied)), Style.WARN).show(); + 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, - getString(R.string.error_nfc_data_not_found)), Style.WARN).show(); + 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, - getString(R.string.error_nfc_unknown)), Style.WARN).show(); + case 0x6F00: { + Notify.create(this, getString(R.string.error_nfc_unknown), Style.WARN).show(); break; } default: diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 8e2f0f5e2..88d44d2e4 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1309,14 +1309,17 @@ "Import" Different key stored on YubiKey! "NFC Error: %s" - "Incorrect PIN; %d tries remaining." - "Smart card in termination state" - "Wrong length for sent / received data" - "Conditions of use not satisfied" - "Security status not satisfied" - "PIN blocked after too many attempts" - "Key or object not found" - "Unknown Error" + "NFC: Incorrect PIN; %d tries remaining." + "NFC: Smart card in termination state" + "NFC: Wrong length for sent / received data" + "NFC: Conditions of use not satisfied" + "NFC: Security status not satisfied" + "NFC: PIN blocked after too many attempts" + "NFC: Key or object not found" + "NFC: Unknown Error" + "NFC: Card reported invalid data" + "NFC: Card expected last command in a chain" + "NFC: Card reported invalid %s byte" Default PIN was rejected! Error creating temporary file. Bluetooth sharing will fail. "Filenames are encrypted." -- cgit v1.2.3 From 0b3317600bf7641425cfacb9709333c11a897414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 16 Jun 2015 00:08:17 +0200 Subject: Cleanup in build.gradle --- OpenKeychain/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 3c239d44b..511183d10 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -40,7 +40,7 @@ dependencies { compile 'com.jpardogo.materialtabstrip:library:1.0.9' compile 'com.getbase:floatingactionbutton:1.9.0' compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0' - compile "com.splitwise:tokenautocomplete:1.3.3@aar" + compile 'com.splitwise:tokenautocomplete:1.3.3@aar' compile 'se.emilsjolander:stickylistheaders:2.6.0' compile 'org.sufficientlysecure:html-textview:1.1' compile 'com.mikepenz.materialdrawer:library:2.8.2@aar' -- cgit v1.2.3 From 66e7876abd3c4a5a3d1002386d6445e64e91ae97 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 11 Jun 2015 18:30:39 +0200 Subject: add ToolableViewAnimator --- .../keychain/ui/widget/ToolableViewAnimator.java | 61 ++++++++++++++++++++++ OpenKeychain/src/main/res/values/attr.xml | 4 ++ 2 files changed, 65 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java new file mode 100644 index 000000000..44f2b7c3e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 Vincent Breitmoser + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.sufficientlysecure.keychain.ui.widget; + + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ViewAnimator; + +import org.sufficientlysecure.keychain.R; + + +/** This view is essentially identical to ViewAnimator, but allows specifying the initial view + * for preview as an xml attribute. */ +public class ToolableViewAnimator extends ViewAnimator { + + private int mInitChild = -1; + + public ToolableViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs); + + if (isInEditMode()) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ToolableViewAnimator, defStyleAttr, 0); + mInitChild = a.getInt(R.styleable.ToolableViewAnimator_initialView, -1); + a.recycle(); + } + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (isInEditMode() && mInitChild-- > 0) { + return; + } + super.addView(child, index, params); + } +} diff --git a/OpenKeychain/src/main/res/values/attr.xml b/OpenKeychain/src/main/res/values/attr.xml index 7a2f3054e..04642cabb 100644 --- a/OpenKeychain/src/main/res/values/attr.xml +++ b/OpenKeychain/src/main/res/values/attr.xml @@ -1,6 +1,10 @@ + + + + -- cgit v1.2.3 From 5e4842ab64733fbf07e32af0e6c5f21fad743874 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 13 Jun 2015 21:50:57 +0200 Subject: fix instrumentation test(s) --- OpenKeychain/build.gradle | 12 +++++++---- .../keychain/CreateKeyActivityTest.java | 23 ++++++++++++---------- 2 files changed, 21 insertions(+), 14 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 511183d10..b640e7ebe 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -21,10 +21,14 @@ dependencies { testCompile 'org.robolectric:robolectric:3.0-rc3' // UI testing with Espresso - androidTestCompile 'com.android.support.test:runner:0.2' - androidTestCompile 'com.android.support.test:rules:0.2' - androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1' - androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.1' + androidTestCompile 'com.android.support.test:runner:0.3' + androidTestCompile 'com.android.support.test:rules:0.3' + androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2' + androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2') { + exclude group: 'com.android.support', module: 'appcompat' + exclude group: 'com.android.support', module: 'support-v4' + exclude module: 'recyclerview-v7' + } // Temporary workaround for bug: https://code.google.com/p/android-test-kit/issues/detail?id=136 // from https://github.com/googlesamples/android-testing/blob/master/build.gradle#L21 diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java index c3741fdef..a20b61cf3 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java @@ -17,14 +17,11 @@ package org.sufficientlysecure.keychain; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; import android.text.method.HideReturnsTransformationMethod; import android.text.method.PasswordTransformationMethod; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import static android.support.test.espresso.Espresso.onView; @@ -43,18 +40,24 @@ import static org.hamcrest.Matchers.allOf; import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withError; import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withTransformationMethod; -@RunWith(AndroidJUnit4.class) -public class CreateKeyActivityTest { +@LargeTest +public class CreateKeyActivityTest extends ActivityInstrumentationTestCase2 { public static final String SAMPLE_NAME = "Sample Name"; public static final String SAMPLE_EMAIL = "sample_email@gmail.com"; public static final String SAMPLE_ADDITIONAL_EMAIL = "sample_additional_email@gmail.com"; public static final String SAMPLE_PASSWORD = "sample_password"; - @Rule - public ActivityTestRule mActivityRule = new ActivityTestRule<>(CreateKeyActivity.class); + public CreateKeyActivityTest() { + super(CreateKeyActivity.class); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + getActivity(); + } - @Test public void testCreateMyKey() { // Clicks create my key onView(withId(R.id.create_key_create_key_button)) -- cgit v1.2.3 From 2c47035e02c069cc5f44fc1f4e0da7d5e8bc099d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 13 Jun 2015 22:25:02 +0200 Subject: (minor) layout changes --- OpenKeychain/src/main/res/layout/create_key_passphrase_fragment.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/res/layout/create_key_passphrase_fragment.xml b/OpenKeychain/src/main/res/layout/create_key_passphrase_fragment.xml index 48b86765a..abfb2861b 100644 --- a/OpenKeychain/src/main/res/layout/create_key_passphrase_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_key_passphrase_fragment.xml @@ -34,7 +34,8 @@ android:inputType="textPassword" android:hint="@string/label_passphrase" android:ems="10" - android:layout_gravity="center_horizontal" /> + android:layout_gravity="center_horizontal" + /> Date: Sun, 14 Jun 2015 12:12:28 +0200 Subject: update instrumentation test to JUnit4 --- OpenKeychain/build.gradle | 3 +-- .../org/sufficientlysecure/keychain/CreateKeyActivityTest.java | 10 +++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index b640e7ebe..ca1c30d40 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -143,9 +143,8 @@ android { // Reference them in .xml files. resValue "string", "account_type", "org.sufficientlysecure.keychain.debug.account" - // Disabled: only works for androidTest not test! // Enable code coverage (Jacoco) - //testCoverageEnabled true + testCoverageEnabled true } } diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java index a20b61cf3..f85523eb7 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java @@ -17,11 +17,16 @@ package org.sufficientlysecure.keychain; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.LargeTest; import android.text.method.HideReturnsTransformationMethod; import android.text.method.PasswordTransformationMethod; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import static android.support.test.espresso.Espresso.onView; @@ -40,6 +45,7 @@ import static org.hamcrest.Matchers.allOf; import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withError; import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withTransformationMethod; +@RunWith(AndroidJUnit4.class) @LargeTest public class CreateKeyActivityTest extends ActivityInstrumentationTestCase2 { @@ -52,12 +58,14 @@ public class CreateKeyActivityTest extends ActivityInstrumentationTestCase2 Date: Sun, 14 Jun 2015 15:05:47 +0200 Subject: stash away stuff --- OpenKeychain/build.gradle | 2 + .../src/androidTest/assets/valodim.pub.asc | 1032 ++++++++++++++++++++ .../keychain/EncryptDecryptTests.java | 217 ++++ .../keychain/actions/CustomActions.java | 54 + 4 files changed, 1305 insertions(+) create mode 100644 OpenKeychain/src/androidTest/assets/valodim.pub.asc create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index ca1c30d40..f85678b67 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -197,6 +197,8 @@ android { } } +// apply plugin: 'spoon' + task jacocoTestReport(type:JacocoReport) { group = "Reporting" description = "Generate Jacoco coverage reports" diff --git a/OpenKeychain/src/androidTest/assets/valodim.pub.asc b/OpenKeychain/src/androidTest/assets/valodim.pub.asc new file mode 100644 index 000000000..e9fe0518a --- /dev/null +++ b/OpenKeychain/src/androidTest/assets/valodim.pub.asc @@ -0,0 +1,1032 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +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 +myY5jQtI/Tgt2rMRXPVizQYY/b3z/hZQK4A5TigvFjlfMozs35kOpOuccqf2FLWX +eyOirbZrBp7A9tdKveKVSqxMl+0DZjI3fXD3dw6wBrFLWlUvYKnj8aGh8xRYnV8Q +CthNJoFWKiA5hRW0qUasKY4w3DbVS5F2ozyuL0G/FtK3uEL53c8FGqtH74VqX53K +swK/5SlQPWhEsNk+kl3TZfmVWbDC3RYeXhElBEqSmf4QrmYMYQMbRUECLJPlF/8/ +JBimtVW6CVBu+fR4D8qMXWoGeMJIEWWNKAgOtcwW0MeciobbHl2tFVCO/DkxvYSG +6SLdHxoymAqpxkIJNJDU+FuoNIZpPdJtnfZ9H2QEBz7iPkno1k5f545Chr//m4Am +Z5EKjsYuH0BCso7gLljhtCpWYWxvZGltIFNreXdhbGtlciA8dmFsb2RpbUBtdWdl +bmd1aWxkLmNvbT6JAjgEEwECACIFAlNEOE0CGwMGCwkIBwMCBhUIAgkKCwQWAgMB +Ah4BAheAAAoJEHvRgyDerfoRppwQAIAOCdK/ELZoiXn2trTzBUPq1PNvY5BNHgbH +1RQfjP80ljJESfiMHZWBhk3ASgveg/fOhx9DiUXZyrccRR8ZFknReCw4CD+x/4hT +8vhiufk+palRK+0XaokE6/igKUCV8WsBcF9EgEDBHDx4Vo17DA2QGlOJ1eGuuJJE +db7qMIZq6HxJjKgzD9q+djLz2LZS5mSTzPQiJPFZo0c5MDmGCyIpPed8zAxDhETm +WdOZM4Sq7aupWDiGf6dGJjPpuWMOGbvwlHQmlCMvBJVsgg+fKBT9yn/iJqUphjam +cyKQxgAXvEDUl28q9vXlkViMb358XTrAmWxmqlh5Acf4ESNZmEDqjdaCHYWbpFTF +NUH6NnEGYpuAu9ybJGV4ruzxZKM4B0Fkq6ZEEAAW0Gd7oIg+sahmQ/f7pPM7xj5z +IqG75tPTS7yHyZh38rUaAaRhuoKZoa4D7SCQ5nG3UnOHKGZODV4ZgiPAHt7YyGxg +1HQvvxmRq+DUdKFFl6PYYiVp2VWRifK3cVTS3rjSSU6tHRDje3sYvdCdzVtOJ2XT +b+XYTCmAU6jTRxQJrAlIZtJxvbvy1Zfp1G3/K1F4dsEQwZ0HGPAVo6WG+TjNL2uz +Qv3i60Xm/R1m/PXf0Dgwpst8IZMp0Yr36+zoFzifb6uUNehddS9avH+LPZg6OKhb +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 +aMd86lN6uit3 +=nxfb +-----END PGP PUBLIC KEY BLOCK----- diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java new file mode 100644 index 000000000..6dd6dd91f --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain; + + +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.matcher.ViewMatchers; +import android.support.test.runner.AndroidJUnit4; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; + +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.MainActivity; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDrawer; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(AndroidJUnit4.class) +@LargeTest +public class EncryptDecryptTests extends ActivityInstrumentationTestCase2 { + + public static final String SAMPLE_NAME = "Sample Name"; + public static final String SAMPLE_EMAIL = "sample_email@gmail.com"; + public static final String SAMPLE_ADDITIONAL_EMAIL = "sample_additional_email@gmail.com"; + public static final String SAMPLE_PASSWORD = "sample_password"; + private MainActivity mActivity; + + public EncryptDecryptTests() { + super(MainActivity.class); + } + + @Before + public void setUp() throws Exception { + super.setUp(); + injectInstrumentation(InstrumentationRegistry.getInstrumentation()); + mActivity = getActivity(); + } + + @Test + public void test01ImportKeys() throws Exception { + + UncachedKeyRing pubkey = readRingFromResource("valodim.pub.asc"); + new ProviderHelper(mActivity).savePublicKeyRing(pubkey); + + // open drawer + onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); + + // go to encrypt/decrypt overview + onView(ViewMatchers.withText(R.string.nav_keys)).perform(click()); + + /* + // Clicks create my key + onView(withId(R.id.create_key_create_key_button)) + .perform(click()); + + // Clicks next with empty name + onView(withId(R.id.create_key_next_button)) + .perform(click()); + onView(withId(R.id.create_key_name)) + .check(matches(withError(R.string.create_key_empty))); + + // Types name and clicks next + onView(withId(R.id.create_key_name)) + .perform(typeText(SAMPLE_NAME)); + onView(withId(R.id.create_key_next_button)) + .perform(click()); + + // Clicks next with empty email + onView(withId(R.id.create_key_next_button)) + .perform(click()); + onView(withId(R.id.create_key_email)) + .check(matches(withError(R.string.create_key_empty))); + + // Types email + onView(withId(R.id.create_key_email)) + .perform(typeText(SAMPLE_EMAIL)); + + // Adds same email as additional email and dismisses the snackbar + onView(withId(R.id.create_key_add_email)) + .perform(click()); + onView(withId(R.id.add_email_address)) + .perform(typeText(SAMPLE_EMAIL)); + onView(withText(android.R.string.ok)) + .inRoot(isDialog()) + .perform(click()); + onView(allOf(withId(R.id.sb__text), withText(R.string.create_key_email_already_exists_text))) + .check(matches(isDisplayed())); + onView(allOf(withId(R.id.sb__text), withText(R.string.create_key_email_already_exists_text))) + .perform(swipeLeft()); + + // Adds additional email + onView(withId(R.id.create_key_add_email)) + .perform(click()); + onView(withId(R.id.add_email_address)) + .perform(typeText(SAMPLE_ADDITIONAL_EMAIL)); + onView(withText(android.R.string.ok)) + .inRoot(isDialog()) + .perform(click()); + onView(withId(R.id.create_key_emails)) + .check(matches(hasDescendant(allOf(withId(R.id.create_key_email_item_email), withText(SAMPLE_ADDITIONAL_EMAIL))))); + + // Removes additional email and clicks next + onView(allOf(withId(R.id.create_key_email_item_delete_button), hasSibling(allOf(withId(R.id.create_key_email_item_email), withText(SAMPLE_ADDITIONAL_EMAIL))))) + .perform(click()) + .check(doesNotExist()); + onView(withId(R.id.create_key_next_button)) + .perform(click(click())); + + // Clicks next with empty password + onView(withId(R.id.create_key_next_button)) + .perform(click()); + onView(withId(R.id.create_key_passphrase)) + .check(matches(withError(R.string.create_key_empty))); + + // Types password + onView(withId(R.id.create_key_passphrase)) + .perform(typeText(SAMPLE_PASSWORD)); + + // Clicks next with empty confirm password + onView(withId(R.id.create_key_next_button)) + .perform(click()); + onView(withId(R.id.create_key_passphrase_again)) + .check(matches(withError(R.string.create_key_passphrases_not_equal))); + + // Types confirm password + onView(withId(R.id.create_key_passphrase_again)) + .perform(typeText(SAMPLE_PASSWORD)); + + // Clicks show password twice and clicks next + onView(withId(R.id.create_key_show_passphrase)) + .perform(click()); + onView(withId(R.id.create_key_passphrase)) + .check(matches(withTransformationMethod(HideReturnsTransformationMethod.class))); + onView(withId(R.id.create_key_passphrase_again)) + .check(matches(withTransformationMethod(HideReturnsTransformationMethod.class))); + onView(withId(R.id.create_key_show_passphrase)) + .perform(click()); + onView(withId(R.id.create_key_passphrase)) + .check(matches(withTransformationMethod(PasswordTransformationMethod.class))); + onView(withId(R.id.create_key_passphrase_again)) + .check(matches(withTransformationMethod(PasswordTransformationMethod.class))); + onView(withId(R.id.create_key_next_button)) + .perform(click()); + + // Verifies name and email + onView(withId(R.id.name)) + .check(matches(withText(SAMPLE_NAME))); + onView(withId(R.id.email)) + .check(matches(withText(SAMPLE_EMAIL))); + + // Verifies backstack + onView(withId(R.id.create_key_back_button)) + .perform(click()); + onView(withId(R.id.create_key_back_button)) + .perform(click()); + onView(withId(R.id.create_key_back_button)) + .perform(click()); + + onView(withId(R.id.create_key_name)) + .check(matches(withText(SAMPLE_NAME))); + onView(withId(R.id.create_key_next_button)) + .perform(click()); + + onView(withId(R.id.create_key_email)) + .check(matches(withText(SAMPLE_EMAIL))); + onView(withId(R.id.create_key_next_button)) + .perform(click()); + + // TODO: Uncomment when fixed in main +// onView(withId(R.id.create_key_passphrase)) +// .check(matches(withText(SAMPLE_PASSWORD))); +// onView(withId(R.id.create_key_passphrase_again)) +// .check(matches(withText(SAMPLE_PASSWORD))); + onView(withId(R.id.create_key_next_button)) + .perform(click()); + + onView(withId(R.id.name)) + .check(matches(withText(SAMPLE_NAME))); + onView(withId(R.id.email)) + .check(matches(withText(SAMPLE_EMAIL))); + + // Clicks create key + onView(withId(R.id.create_key_next_button)) + .perform(click()); + */ + + } + + UncachedKeyRing readRingFromResource(String name) throws Exception { + return UncachedKeyRing.fromStream(getInstrumentation().getContext().getAssets().open(name)).next(); + } + +} diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java new file mode 100644 index 000000000..13c092b85 --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java @@ -0,0 +1,54 @@ +package org.sufficientlysecure.keychain.actions; + + +import android.support.test.espresso.UiController; +import android.support.test.espresso.ViewAction; +import android.support.test.espresso.matcher.ViewMatchers; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.view.View; + +import org.hamcrest.Matcher; + + +public abstract class CustomActions { + + public static ViewAction actionOpenDrawer() { + return new ViewAction() { + @Override + public Matcher getConstraints() { + return ViewMatchers.isAssignableFrom(DrawerLayout.class); + } + + @Override + public String getDescription() { + return "open drawer"; + } + + @Override + public void perform(UiController uiController, View view) { + ((DrawerLayout) view).openDrawer(GravityCompat.START); + } + }; + } + + public static ViewAction actionCloseDrawer() { + return new ViewAction() { + @Override + public Matcher getConstraints() { + return ViewMatchers.isAssignableFrom(DrawerLayout.class); + } + + @Override + public String getDescription() { + return "close drawer"; + } + + @Override + public void perform(UiController uiController, View view) { + ((DrawerLayout) view).closeDrawer(GravityCompat.START); + } + }; + } + +} \ No newline at end of file -- cgit v1.2.3 From 7998b2a262aa809f9879d51e6edc55ddadcf2699 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 14 Jun 2015 18:06:57 +0200 Subject: instrument: add helper method for snackbar checking --- .../keychain/actions/CustomMatchers.java | 27 ++++++++++++++++++++++ .../keychain/ui/util/Notify.java | 23 ++++++------------ 2 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomMatchers.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomMatchers.java new file mode 100644 index 000000000..29e875b95 --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomMatchers.java @@ -0,0 +1,27 @@ +package org.sufficientlysecure.keychain.actions; + + +import android.support.annotation.ColorRes; +import android.support.test.espresso.matcher.BoundedMatcher; +import android.view.View; + +import com.nispok.snackbar.Snackbar; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +public abstract class CustomMatchers { + + public static Matcher withSnackbarLineColor(@ColorRes final int colorRes) { + return new BoundedMatcher(Snackbar.class) { + public void describeTo(Description description) { + description.appendText("with color resource id: " + colorRes); + } + + @Override + public boolean matchesSafely(Snackbar snackbar) { + return snackbar.getResources().getColor(colorRes) == snackbar.getLineColor(); + } + }; + } + +} 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); } } -- cgit v1.2.3 From 34c72520482aa03586d20a9edb4491b49d75b236 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 14 Jun 2015 18:08:18 +0200 Subject: add extra to disregard first-time dialog to main activity --- .../main/java/org/sufficientlysecure/keychain/ui/MainActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'OpenKeychain') 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); -- cgit v1.2.3 From fe4659f8d6ef5b0520ba391deb050f64f878aeed Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 14 Jun 2015 18:09:14 +0200 Subject: instrument: add helper for snackbar check (2) --- .../keychain/actions/CheckHelpers.java | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CheckHelpers.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CheckHelpers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CheckHelpers.java new file mode 100644 index 000000000..a950ceeaa --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CheckHelpers.java @@ -0,0 +1,31 @@ +package org.sufficientlysecure.keychain.actions; + + +import android.support.annotation.StringRes; + +import org.hamcrest.CoreMatchers; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; +import static android.support.test.espresso.matcher.ViewMatchers.withClassName; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.sufficientlysecure.keychain.actions.CustomMatchers.withSnackbarLineColor; + + +abstract public class CheckHelpers { + + public static void checkSnackbar(Style style, @StringRes Integer text) { + + onView(withClassName(CoreMatchers.endsWith("Snackbar"))) + .check(matches(withSnackbarLineColor(style.mLineColor))); + + if (text != null) { + onView(withClassName(CoreMatchers.endsWith("Snackbar"))) + .check(matches(hasDescendant(withText(text)))); + } + + } + +} -- cgit v1.2.3 From 908545e5212fc62cb2c1cb7b3dcac2311e43524a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 14 Jun 2015 18:09:52 +0200 Subject: instrument: add test for symmetric text encrypt/decrypt --- .../keychain/EncryptDecryptSymmetricTests.java | 124 +++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java new file mode 100644 index 000000000..539c1a4ea --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain; + + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.assertion.ViewAssertions; +import android.support.test.espresso.core.deps.guava.collect.Iterables; +import android.support.test.espresso.matcher.ViewMatchers; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; +import android.support.test.runner.lifecycle.Stage; +import android.test.suitebuilder.annotation.LargeTest; + +import com.nispok.snackbar.Snackbar; +import org.hamcrest.CoreMatchers; +import org.hamcrest.Matcher; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.sufficientlysecure.keychain.actions.CheckHelpers; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.MainActivity; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.ProgressScaler; + +import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; +import static android.support.test.espresso.Espresso.pressBack; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.typeText; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withChild; +import static android.support.test.espresso.matcher.ViewMatchers.withClassName; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.sufficientlysecure.keychain.actions.CheckHelpers.checkSnackbar; +import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDrawer; + + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(AndroidJUnit4.class) +@LargeTest +public class EncryptDecryptSymmetricTests { + + public static final String PASSPHRASE = "fn9nf8wnaf"; + + @Rule + public final ActivityTestRule mActivity + = new ActivityTestRule(MainActivity.class) { + @Override + protected Intent getActivityIntent() { + Intent intent = super.getActivityIntent(); + intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true); + return intent; + } + }; + + @Test + public void test01ImportKeys() throws Exception { + + MainActivity activity = mActivity.getActivity(); + + // navigate to encrypt/decrypt + onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); + onView(ViewMatchers.withText(R.string.nav_encrypt_decrypt)).perform(click()); + onView(withId(R.id.encrypt_text)).perform(click()); + + { + String text = "how much wood"; + onView(withId(R.id.encrypt_text_text)).perform(typeText(text)); + + openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext()); + onView(withText(R.string.label_symmetric)).perform(click()); + + onView(withId(R.id.passphrase)).perform(typeText(PASSPHRASE)); + + onView(withId(R.id.encrypt_copy)).perform(click()); + + checkSnackbar(Style.ERROR, R.string.passphrases_do_not_match); + + onView(withId(R.id.passphraseAgain)).perform(typeText(PASSPHRASE)); + + onView(withId(R.id.encrypt_text_text)).check(matches(withText(text))); + + onView(withId(R.id.encrypt_copy)).perform(click()); + + checkSnackbar(Style.OK, R.string.msg_se_success); + } + + // go to decrypt from clipboard view + pressBack(); + onView(withId(R.id.decrypt_from_clipboard)).perform(click()); + + // TODO fix thing, finish test + + } + +} -- cgit v1.2.3 From db0266c0aeefb5f788625f6ee2a7be733cace454 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 14 Jun 2015 20:21:13 +0200 Subject: instrument: work on instrumentation tests --- OpenKeychain/src/androidTest/assets/x.sec.asc | 156 ++++++++++++++ .../keychain/EncryptDecryptSymmetricTests.java | 2 +- .../keychain/EncryptDecryptTests.java | 225 ++++++--------------- .../keychain/actions/CustomActions.java | 48 +++++ .../keychain/ui/EncryptModeAsymmetricFragment.java | 1 - 5 files changed, 269 insertions(+), 163 deletions(-) create mode 100644 OpenKeychain/src/androidTest/assets/x.sec.asc (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/assets/x.sec.asc b/OpenKeychain/src/androidTest/assets/x.sec.asc new file mode 100644 index 000000000..57a204cfc --- /dev/null +++ b/OpenKeychain/src/androidTest/assets/x.sec.asc @@ -0,0 +1,156 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFTxJOIBEACpXpXjz5ivyR/uWJexJXZrLHrvBRcivQSxzvy5Owdun8MGOxzo +YTPAJzskZH4Gg9MIQj9puUke8A59o9KtNMAd3Szp6aQs3lv/eGOw0DUe6g8a6kJ+ +oQsHMYYLnyuYrOvQrBewKhh4FynkryvUZCL25dgrMHeTeMAeslCscdT6JYd2gxgi +o0+Bn8R6BonS7hTZStfAOYB0SO7X+7R0CpPsMiHtNZASzLRkk53dB9AO6m81CurP +qh1oPjc7j1QIiwCszSD8IxuR3fH6nyUlMtqx/mRmKNlbIYnr3sN5VAMp7THq/Qjt +9XDs1XHBE+0H+yg7K4fPamSJsxy9zPq/rA9vy4pQIMzxugxeWEn4OPWtguRSYtI1 +WA+Ki93QJ+DVRlpMvz/MA71ZkvWUHpFnIssU52NdHZrIZ21KCo7PiPRY9tQkVhz5 +S39a8npSeCIwZ9bH3UGKrRyPkhal59rjPqhZky1X+O2iTfbbFrPE+I4b46pWX9XU +UgXDnUh8Qeymhh0f1DTcbquW2nwU/R1quKjw/TCZQxKnkAzCXsVFk2QoIG1qDVrU +YlKXTXoYaRfzhMSYSvh7JHqdldB+MyKAbXpLtZsYIlLkuqe/KI0IPfO69ajDxLOr ++wTtuA9T1SICGmQkjYkQiqVy4y7vDW4Zw143sPXreZhiBbLBoWAIJ/JcbQARAQAB +tAVYIDx4PokCNwQTAQoAIQULCQgHAwYVCgkLCAMEFgIDAQIZAQWCVPEk4wKeAQKb +AQAKCRCdYE0vMQcWo/TGD/9SEfE7LxWw4pPNTpVTECDztpLGrj7kCr0anO6XnCqL +3OYoZz5vZvKBmunoPT7VqerQIZCnhB0b0ZOI+l51J03Xwvfy1+MIQCnZz++JY94B +h5dv3QAj8WUJJ6KfKrAxoXGU67mNhW5oe3mXQd3/a0JjmjV2yzFFOS4edyWPQal9 +lY6MpOKfEIiD6uRqh8A1A9jAstJ9c5XHVtuBv265DEK5tMyAU3cmtm0pEzgmvPoG +kxo1vDAhP9GKb+DcBrB6XuZ7Kahl+kDpEhmuI6drxJBhez169aE43dK7G3X1W78f +OyO0C4jWAy1kVj4aYT4qgJj8TwRoAwlzX5RqmK4RW17sX3nOlff/FQclepawOrU+ +LO5ZgbQ4qG2yDJSJ+tcS2fIO1MoI5vPa7DIVEpMM7DbrPYVy0Ix/xv80MwKQhnWs +P5tLRGuJ7JbKPzqvBtG7xWow5isOMkqeBkU0yUr59tAtHwM6c3Vi2at9YBiraqBY +3mgEukIuNpSuhFSBhniVUovVGgj8LCLDL+mpuc9+HUzYWJKGks+eGY0Od+dJjgh+ +wXQk0rTTkY80kKpIjREDOVuPRhsw5OYb63fbiaYwormPx4pXv2mitOYNAXy3YNpR +Xl5MvObYLQugpqtyjpijyyANbsHKWwClkL/vxnbcfRXF307NQGSwhs3gKpSuovVE +edEkI2QBI0VncGdwaWQ6QGRuczp0ZXN0Lm11Z2VuZ3VpbGQuY29tiQIcBBMBCgAG +BYJU9KuzAAoJEJ1gTS8xBxajnPEP+gL3MasL9GcXt0c93QkQsRay8IMCspM+Qt/X +rfoUSJb41V4nqeyumX/kRmY7/eMZJnxIJgn8aWmRjiPhasUMBfeXmm4RAmwHEkFP +6nw288dHIlKgRaAMwfs44thKxPKTkscLiSqbmrRhLULW840pIZkgtOLmhH2tiaa7 +w/hgDCMgHfwZaSlQkEDfvAef7i61itNdBlL0e0CelzNL0Uc5P6b/Nvn5uWlrLIHZ +evOx8dswhmwz25D7KZQPh91JpAqoLk5hj3/DAbVxeUFK/Zt9o/bu+ij57qSfRif3 +HlLydH98FFcetqLbGDt1/Eiea3tpqjK5Xgf+HOppQo2MpOCP64xNejGWnKx6M7yF +A/yiI4Ahe4uP9+SXar0FPLGXAfyc/pSrRN94dY+iXpWPCSlNRNAOyr1mxPCvw9MM +C5wYnfLLSWUMPYv9rQyr1pvYyxe2r0Fla6JAkRy5nMnR1RdorwafeMcyQi3nllBS +S0wzmagus1W2qqRbbOR8J5CXkkjbw9vV0vXFnCHq93ms9Ebnhr5rNzVndKnxVCmK +hftW7gFz8Qz3tG66eSWA/6VrRvfUNfR5cUBpVOARqxqYz7OPrGg0Lkfw+JPQX8v/ +WMIfM6Jsj/koJmOb2y/h0rJ+iTBofPCoGONKIzlOK5c8bhSmU/27KX1xBacTgN9Q +hlulIbZs0SsqZAAS1odwZ3BpZCtjb29raWU6QGRuczp0ZXN0Lm11Z2VuZ3VpbGQu +Y29tiQIcBBMBCgAGBYJU901MAAoJEJ1gTS8xBxajHKAP/3WDeUfBkuq50v3E8SBx +qDHXomPncNOccRPS1/DYwxVp5JYE8NsFW2rlmeJiy4Rhm4MnptUFUTPMBfYOcHvT +oWr/3Gz0y5ik5oiPC5hAXWCPrNkdtgBugBByL+0+w2PWKQqwBIc9ef+7aIh28dKn +6zr71Au+DXJKqYz/jOqY3IJGzBkydxOPSy83WNPm9OR/zfhCOoCTDAy+0TGxYgjW +Idi4k4yDRWhAeF+GqJRuDf1RKZxPL1MjwhX0Lv4ndR+V3sECRVh6HRIBGYYti8kU +0Q9jdbYjU+guestyyvsWQBOaGRP+gGQY+fYdHb2YUK6jMcndPxbRonjaRY5Bcc0J +NAYAqFv/WyR7hd4SWXI8Zg3u+GV7x8ZbnUOwfvXBVvZohbycpoT7WVOPRhQ2CQ5P +uS2E5QaoA6fUfH2qUAaxlvnfMRPL9E1svIEFXMtf1GqO6hzzJWV6Qw59HODTy568 +StloM6IVnlolJfxf7+aEQx+KrmxJqVVWlbZ5u5gzQa+Mak0N5F6m8w4VftyHT0W6 +RinNh3IGbk64+uXYzAUhJ6badfXrrxTP0wkjPPPjNC/BKfPCzYhZnyGLCuFwlOqE +o1Nq6qMzSHOlxR08ycX0MLNEfWsqu2CV8YGLYThejOr8JSjyXM3ANHNB+AZ5/eeY +AGUc5XgxTbq/MpnytiBzaZfw0Tw7ZAAS1odwZ3BpZCtjb29raWU6Z2VuZXJpY0Bo +dHRwczovL211Z2VuZ3VpbGQuY29tL3BncGtleS50eHSJAhwEEwEIAAYFglT4KOIA +CgkQnWBNLzEHFqOZJQ//UL+/ZfikmVA8DDIbbd8beIgJ0uRZgQ4aVuxRNiWhxQbl +5KPQaiztrwr3ESgyf3HjjUyGhg5/UP+ObvloCqcwCphSrBNxHiEp31jLin7QfWLp +vzPOjtMJNEeq3yeXq+YpHw2VZ+nod+XD785YtLBqq1SipNSZEifRYnsLhqQOj6cB +t0F2RLBm5afLbd4KjelT7W7229lYlGJkoQEwrmVYASiMfeBDBnHJ1Xgp1P0dm1gS +ERVSzV2qW6w0/psOHq4cnq4XWSn/IRW3UYfHIDDZJG125keYoaJAbBnzC+OO4eV2 +KheY5dkmaeT0RmgzpEyJq5PRDNpnoXvSYuC9KGiNTbCiQ3liJnUJslbzDwvTyTfa +wxUba4dXhpcSE8mjXa/9/vMJER5yW0zzkSa+fjIIZcL0PQqlTeDBzFMSA9Ut9Y+m +6vdGwelIeyb38dWvsDC1NUUR2pydTsRtFM1o7jUtGoovQlLh5/9x/YDh8vqpzip3 +L5SSvN4RdXN4P+EZor4xfi0PVbHoDZusxyPfKEFlCnX6k9qEaWplacLbl8M8mtpv +++3vYVW6hA3rK/6XdftFDJpbmj+zTXvrATE/lvPcJHiswiuMLx9BPoEiv1MGXTyE +6TxB5H7L1eCQWfbZYnkEWiuXlfDyofvDfufiGTsBAQzN3ePuhYUNznUB+1syD4E= +=Sxs8 +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=AYKY +-----END PGP PRIVATE KEY BLOCK----- diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java index 539c1a4ea..fd9f99cf0 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java @@ -82,7 +82,7 @@ public class EncryptDecryptSymmetricTests { }; @Test - public void test01ImportKeys() throws Exception { + public void testSymmetricTextEncryptDecrypt() throws Exception { MainActivity activity = mActivity.getActivity(); diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java index 6dd6dd91f..28fbe3aa9 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java @@ -18,200 +18,103 @@ package org.sufficientlysecure.keychain; -import android.support.test.InstrumentationRegistry; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; import android.support.test.espresso.matcher.ViewMatchers; +import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; -import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.LargeTest; import org.junit.Before; import org.junit.FixMethodOrder; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.MainActivity; +import org.sufficientlysecure.keychain.util.ProgressScaler; +import static android.support.test.InstrumentationRegistry.getInstrumentation; import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.typeText; +import static android.support.test.espresso.matcher.RootMatchers.isDialog; +import static android.support.test.espresso.matcher.RootMatchers.isPlatformPopup; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDrawer; +import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken; + @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RunWith(AndroidJUnit4.class) @LargeTest -public class EncryptDecryptTests extends ActivityInstrumentationTestCase2 { - - public static final String SAMPLE_NAME = "Sample Name"; - public static final String SAMPLE_EMAIL = "sample_email@gmail.com"; - public static final String SAMPLE_ADDITIONAL_EMAIL = "sample_additional_email@gmail.com"; - public static final String SAMPLE_PASSWORD = "sample_password"; - private MainActivity mActivity; - - public EncryptDecryptTests() { - super(MainActivity.class); - } +public class EncryptDecryptTests { + + @Rule + public final ActivityTestRule mActivity + = new ActivityTestRule(MainActivity.class) { + @Override + protected Intent getActivityIntent() { + Intent intent = super.getActivityIntent(); + intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true); + return intent; + } + }; @Before public void setUp() throws Exception { - super.setUp(); - injectInstrumentation(InstrumentationRegistry.getInstrumentation()); - mActivity = getActivity(); + Activity activity = mActivity.getActivity(); + + // import these two, make sure they're there + importKeysFromResource(activity, "x.sec.asc"); } @Test - public void test01ImportKeys() throws Exception { - - UncachedKeyRing pubkey = readRingFromResource("valodim.pub.asc"); - new ProviderHelper(mActivity).savePublicKeyRing(pubkey); + public void testTextEncryptDecrypt() throws Exception { - // open drawer + // navigate to encrypt/decrypt onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); + onView(ViewMatchers.withText(R.string.nav_encrypt_decrypt)).perform(click()); + onView(withId(R.id.encrypt_text)).perform(click()); - // go to encrypt/decrypt overview - onView(ViewMatchers.withText(R.string.nav_keys)).perform(click()); - - /* - // Clicks create my key - onView(withId(R.id.create_key_create_key_button)) - .perform(click()); - - // Clicks next with empty name - onView(withId(R.id.create_key_next_button)) - .perform(click()); - onView(withId(R.id.create_key_name)) - .check(matches(withError(R.string.create_key_empty))); - - // Types name and clicks next - onView(withId(R.id.create_key_name)) - .perform(typeText(SAMPLE_NAME)); - onView(withId(R.id.create_key_next_button)) - .perform(click()); - - // Clicks next with empty email - onView(withId(R.id.create_key_next_button)) - .perform(click()); - onView(withId(R.id.create_key_email)) - .check(matches(withError(R.string.create_key_empty))); - - // Types email - onView(withId(R.id.create_key_email)) - .perform(typeText(SAMPLE_EMAIL)); - - // Adds same email as additional email and dismisses the snackbar - onView(withId(R.id.create_key_add_email)) - .perform(click()); - onView(withId(R.id.add_email_address)) - .perform(typeText(SAMPLE_EMAIL)); - onView(withText(android.R.string.ok)) - .inRoot(isDialog()) - .perform(click()); - onView(allOf(withId(R.id.sb__text), withText(R.string.create_key_email_already_exists_text))) - .check(matches(isDisplayed())); - onView(allOf(withId(R.id.sb__text), withText(R.string.create_key_email_already_exists_text))) - .perform(swipeLeft()); - - // Adds additional email - onView(withId(R.id.create_key_add_email)) - .perform(click()); - onView(withId(R.id.add_email_address)) - .perform(typeText(SAMPLE_ADDITIONAL_EMAIL)); - onView(withText(android.R.string.ok)) - .inRoot(isDialog()) - .perform(click()); - onView(withId(R.id.create_key_emails)) - .check(matches(hasDescendant(allOf(withId(R.id.create_key_email_item_email), withText(SAMPLE_ADDITIONAL_EMAIL))))); - - // Removes additional email and clicks next - onView(allOf(withId(R.id.create_key_email_item_delete_button), hasSibling(allOf(withId(R.id.create_key_email_item_email), withText(SAMPLE_ADDITIONAL_EMAIL))))) - .perform(click()) - .check(doesNotExist()); - onView(withId(R.id.create_key_next_button)) - .perform(click(click())); - - // Clicks next with empty password - onView(withId(R.id.create_key_next_button)) - .perform(click()); - onView(withId(R.id.create_key_passphrase)) - .check(matches(withError(R.string.create_key_empty))); - - // Types password - onView(withId(R.id.create_key_passphrase)) - .perform(typeText(SAMPLE_PASSWORD)); - - // Clicks next with empty confirm password - onView(withId(R.id.create_key_next_button)) - .perform(click()); - onView(withId(R.id.create_key_passphrase_again)) - .check(matches(withError(R.string.create_key_passphrases_not_equal))); - - // Types confirm password - onView(withId(R.id.create_key_passphrase_again)) - .perform(typeText(SAMPLE_PASSWORD)); - - // Clicks show password twice and clicks next - onView(withId(R.id.create_key_show_passphrase)) - .perform(click()); - onView(withId(R.id.create_key_passphrase)) - .check(matches(withTransformationMethod(HideReturnsTransformationMethod.class))); - onView(withId(R.id.create_key_passphrase_again)) - .check(matches(withTransformationMethod(HideReturnsTransformationMethod.class))); - onView(withId(R.id.create_key_show_passphrase)) - .perform(click()); - onView(withId(R.id.create_key_passphrase)) - .check(matches(withTransformationMethod(PasswordTransformationMethod.class))); - onView(withId(R.id.create_key_passphrase_again)) - .check(matches(withTransformationMethod(PasswordTransformationMethod.class))); - onView(withId(R.id.create_key_next_button)) - .perform(click()); - - // Verifies name and email - onView(withId(R.id.name)) - .check(matches(withText(SAMPLE_NAME))); - onView(withId(R.id.email)) - .check(matches(withText(SAMPLE_EMAIL))); - - // Verifies backstack - onView(withId(R.id.create_key_back_button)) - .perform(click()); - onView(withId(R.id.create_key_back_button)) - .perform(click()); - onView(withId(R.id.create_key_back_button)) - .perform(click()); - - onView(withId(R.id.create_key_name)) - .check(matches(withText(SAMPLE_NAME))); - onView(withId(R.id.create_key_next_button)) - .perform(click()); - - onView(withId(R.id.create_key_email)) - .check(matches(withText(SAMPLE_EMAIL))); - onView(withId(R.id.create_key_next_button)) - .perform(click()); - - // TODO: Uncomment when fixed in main -// onView(withId(R.id.create_key_passphrase)) -// .check(matches(withText(SAMPLE_PASSWORD))); -// onView(withId(R.id.create_key_passphrase_again)) -// .check(matches(withText(SAMPLE_PASSWORD))); - onView(withId(R.id.create_key_next_button)) - .perform(click()); - - onView(withId(R.id.name)) - .check(matches(withText(SAMPLE_NAME))); - onView(withId(R.id.email)) - .check(matches(withText(SAMPLE_EMAIL))); - - // Clicks create key - onView(withId(R.id.create_key_next_button)) - .perform(click()); - */ + { + // TODO instrument this (difficult because of TokenCompleteView's async implementation) + onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L)); + + String text = "how much wood"; + onView(withId(R.id.encrypt_text_text)).perform(typeText(text)); + + onView(withId(R.id.encrypt_copy)).perform(click()); + } + + // go to decrypt from clipboard view + pressBack(); + onView(withId(R.id.decrypt_from_clipboard)).perform(click()); + + // synchronization with passphrase caching thing doesn't work + onView(withId(R.id.passphrase_passphrase)).inRoot(isPlatformPopup()).perform(typeText("x")); } - UncachedKeyRing readRingFromResource(String name) throws Exception { - return UncachedKeyRing.fromStream(getInstrumentation().getContext().getAssets().open(name)).next(); + static void importKeysFromResource(Context context, String name) throws Exception { + IteratorWithIOThrow stream = UncachedKeyRing.fromStream( + getInstrumentation().getContext().getAssets().open(name)); + + ProviderHelper helper = new ProviderHelper(context); + while(stream.hasNext()) { + UncachedKeyRing ring = stream.next(); + if (ring.isSecret()) { + helper.saveSecretKeyRing(ring, new ProgressScaler()); + } else { + helper.saveSecretKeyRing(ring, new ProgressScaler()); + } + } + } } diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java index 13c092b85..ff9384247 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java @@ -8,11 +8,59 @@ import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.view.View; +import com.tokenautocomplete.TokenCompleteTextView; import org.hamcrest.Matcher; +import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; + +import static android.support.test.InstrumentationRegistry.getTargetContext; public abstract class CustomActions { + public static ViewAction tokenEncryptViewAddToken(long keyId) throws Exception { + CanonicalizedPublicKeyRing ring = + new ProviderHelper(getTargetContext()).getCanonicalizedPublicKeyRing(keyId); + final Object item = new KeyAdapter.KeyItem(ring); + + return new ViewAction() { + @Override + public Matcher getConstraints() { + return ViewMatchers.isAssignableFrom(TokenCompleteTextView.class); + } + + @Override + public String getDescription() { + return "add completion token"; + } + + @Override + public void perform(UiController uiController, View view) { + ((TokenCompleteTextView) view).addObject(item); + } + }; + } + + public static ViewAction tokenViewAddToken(final Object item) { + return new ViewAction() { + @Override + public Matcher getConstraints() { + return ViewMatchers.isAssignableFrom(TokenCompleteTextView.class); + } + + @Override + public String getDescription() { + return "add completion token"; + } + + @Override + public void perform(UiController uiController, View view) { + ((TokenCompleteTextView) view).addObject(item); + } + }; + } + public static ViewAction actionOpenDrawer() { return new ViewAction() { @Override 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..ccc8bfa14 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 -- cgit v1.2.3 From ae199313ee39c27985cca375a7fbd985fe28e1fd Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 14 Jun 2015 22:33:58 +0200 Subject: instrument: change handling in PassphraseDialogActivity to work with espresso --- .../keychain/EncryptDecryptTests.java | 8 +- .../keychain/ui/PassphraseDialogActivity.java | 108 ++++++--------------- 2 files changed, 38 insertions(+), 78 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java index 28fbe3aa9..604c77ece 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java @@ -46,6 +46,7 @@ import static android.support.test.espresso.action.ViewActions.typeText; import static android.support.test.espresso.matcher.RootMatchers.isDialog; import static android.support.test.espresso.matcher.RootMatchers.isPlatformPopup; import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDrawer; import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken; @@ -96,8 +97,11 @@ public class EncryptDecryptTests { pressBack(); onView(withId(R.id.decrypt_from_clipboard)).perform(click()); - // synchronization with passphrase caching thing doesn't work - onView(withId(R.id.passphrase_passphrase)).inRoot(isPlatformPopup()).perform(typeText("x")); + { + onView(withId(R.id.passphrase_passphrase)).perform(typeText("x")); + + onView(withText(R.string.btn_unlock)).perform(click()); + } } 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); -- cgit v1.2.3 From b7834b432697af8026ae403b5841fedde1697a6f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jun 2015 03:33:19 +0200 Subject: fix recursive log in modifySecretKeyRing --- .../sufficientlysecure/keychain/operations/EditKeyOperation.java | 2 +- .../org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) (limited to 'OpenKeychain') 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 { 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/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 -- cgit v1.2.3 From b8305d43dcae87b441e48eb8f47e471e0b0c2781 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jun 2015 03:45:43 +0200 Subject: return actual last log entry, including from sublogentryparcels --- .../keychain/operations/results/OperationResult.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'OpenKeychain') 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 -- cgit v1.2.3 From 71ea52119898f2e9552b542b2bcc40ad430ae8a3 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jun 2015 04:05:07 +0200 Subject: clean up helper code, add withKeyItemId matcher for KeyListAdapter --- .../keychain/EncryptDecryptSymmetricTests.java | 20 +------- .../keychain/EncryptDecryptTests.java | 25 +--------- .../sufficientlysecure/keychain/TestHelpers.java | 55 ++++++++++++++++++++++ .../keychain/actions/CheckHelpers.java | 31 ------------ .../keychain/actions/CustomMatchers.java | 27 ----------- .../keychain/matcher/CustomMatchers.java | 48 +++++++++++++++++++ 6 files changed, 105 insertions(+), 101 deletions(-) create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java delete mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CheckHelpers.java delete mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomMatchers.java create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java index fd9f99cf0..f668472b2 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java @@ -18,33 +18,19 @@ package org.sufficientlysecure.keychain; -import android.app.Activity; -import android.app.Instrumentation; import android.content.Intent; -import android.support.test.InstrumentationRegistry; -import android.support.test.espresso.assertion.ViewAssertions; -import android.support.test.espresso.core.deps.guava.collect.Iterables; import android.support.test.espresso.matcher.ViewMatchers; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; -import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; -import android.support.test.runner.lifecycle.Stage; import android.test.suitebuilder.annotation.LargeTest; -import com.nispok.snackbar.Snackbar; -import org.hamcrest.CoreMatchers; -import org.hamcrest.Matcher; import org.junit.FixMethodOrder; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; -import org.sufficientlysecure.keychain.actions.CheckHelpers; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.MainActivity; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.util.ProgressScaler; import static android.support.test.InstrumentationRegistry.getInstrumentation; import static android.support.test.espresso.Espresso.onView; @@ -53,13 +39,9 @@ import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.typeText; import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withChild; -import static android.support.test.espresso.matcher.ViewMatchers.withClassName; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static org.sufficientlysecure.keychain.actions.CheckHelpers.checkSnackbar; +import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDrawer; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java index 604c77ece..01a748172 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.support.test.espresso.matcher.ViewMatchers; import android.support.test.rule.ActivityTestRule; @@ -32,21 +31,15 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.MainActivity; -import org.sufficientlysecure.keychain.util.ProgressScaler; -import static android.support.test.InstrumentationRegistry.getInstrumentation; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.typeText; -import static android.support.test.espresso.matcher.RootMatchers.isDialog; -import static android.support.test.espresso.matcher.RootMatchers.isPlatformPopup; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDrawer; import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken; @@ -105,20 +98,4 @@ public class EncryptDecryptTests { } - static void importKeysFromResource(Context context, String name) throws Exception { - IteratorWithIOThrow stream = UncachedKeyRing.fromStream( - getInstrumentation().getContext().getAssets().open(name)); - - ProviderHelper helper = new ProviderHelper(context); - while(stream.hasNext()) { - UncachedKeyRing ring = stream.next(); - if (ring.isSecret()) { - helper.saveSecretKeyRing(ring, new ProgressScaler()); - } else { - helper.saveSecretKeyRing(ring, new ProgressScaler()); - } - } - - } - } diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java new file mode 100644 index 000000000..0adc6b264 --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java @@ -0,0 +1,55 @@ +package org.sufficientlysecure.keychain; + + +import android.content.Context; +import android.support.annotation.StringRes; + +import org.hamcrest.CoreMatchers; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.ProgressScaler; + +import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; +import static android.support.test.espresso.matcher.ViewMatchers.withClassName; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSnackbarLineColor; + + +public class TestHelpers { + + + public static void checkSnackbar(Style style, @StringRes Integer text) { + + onView(withClassName(CoreMatchers.endsWith("Snackbar"))) + .check(matches(withSnackbarLineColor(style.mLineColor))); + + if (text != null) { + onView(withClassName(CoreMatchers.endsWith("Snackbar"))) + .check(matches(hasDescendant(withText(text)))); + } + + } + + + static void importKeysFromResource(Context context, String name) throws Exception { + IteratorWithIOThrow stream = UncachedKeyRing.fromStream( + getInstrumentation().getContext().getAssets().open(name)); + + ProviderHelper helper = new ProviderHelper(context); + while(stream.hasNext()) { + UncachedKeyRing ring = stream.next(); + if (ring.isSecret()) { + helper.saveSecretKeyRing(ring, new ProgressScaler()); + } else { + helper.saveSecretKeyRing(ring, new ProgressScaler()); + } + } + + } + +} diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CheckHelpers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CheckHelpers.java deleted file mode 100644 index a950ceeaa..000000000 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CheckHelpers.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.sufficientlysecure.keychain.actions; - - -import android.support.annotation.StringRes; - -import org.hamcrest.CoreMatchers; -import org.sufficientlysecure.keychain.ui.util.Notify.Style; - -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; -import static android.support.test.espresso.matcher.ViewMatchers.withClassName; -import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static org.sufficientlysecure.keychain.actions.CustomMatchers.withSnackbarLineColor; - - -abstract public class CheckHelpers { - - public static void checkSnackbar(Style style, @StringRes Integer text) { - - onView(withClassName(CoreMatchers.endsWith("Snackbar"))) - .check(matches(withSnackbarLineColor(style.mLineColor))); - - if (text != null) { - onView(withClassName(CoreMatchers.endsWith("Snackbar"))) - .check(matches(hasDescendant(withText(text)))); - } - - } - -} diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomMatchers.java deleted file mode 100644 index 29e875b95..000000000 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomMatchers.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.sufficientlysecure.keychain.actions; - - -import android.support.annotation.ColorRes; -import android.support.test.espresso.matcher.BoundedMatcher; -import android.view.View; - -import com.nispok.snackbar.Snackbar; -import org.hamcrest.Description; -import org.hamcrest.Matcher; - -public abstract class CustomMatchers { - - public static Matcher withSnackbarLineColor(@ColorRes final int colorRes) { - return new BoundedMatcher(Snackbar.class) { - public void describeTo(Description description) { - description.appendText("with color resource id: " + colorRes); - } - - @Override - public boolean matchesSafely(Snackbar snackbar) { - return snackbar.getResources().getColor(colorRes) == snackbar.getLineColor(); - } - }; - } - -} diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java new file mode 100644 index 000000000..c023f6411 --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java @@ -0,0 +1,48 @@ +package org.sufficientlysecure.keychain.matcher; + + +import android.support.annotation.ColorRes; +import android.support.test.espresso.matcher.BoundedMatcher; +import android.support.test.internal.util.Checks; +import android.view.View; + +import com.nispok.snackbar.Snackbar; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.sufficientlysecure.keychain.ui.KeyListFragment.KeyListAdapter; +import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; + +import static android.support.test.internal.util.Checks.checkNotNull; + + +public abstract class CustomMatchers { + + public static Matcher withSnackbarLineColor(@ColorRes final int colorRes) { + return new BoundedMatcher(Snackbar.class) { + public void describeTo(Description description) { + description.appendText("with color resource id: " + colorRes); + } + + @Override + public boolean matchesSafely(Snackbar snackbar) { + return snackbar.getResources().getColor(colorRes) == snackbar.getLineColor(); + } + }; + } + + public static Matcher withKeyItemId(final long keyId) { + return new BoundedMatcher(KeyItem.class) { + @Override + public boolean matchesSafely(KeyItem item) { + return item.mKeyId == keyId; + } + + @Override + public void describeTo(Description description) { + description.appendText("with key id: " + keyId); + } + }; + } + +} -- cgit v1.2.3 From 312cb3884826c7b9eee19d83a5581f96a10a7a00 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jun 2015 04:05:41 +0200 Subject: preliminary EditKeyTest --- .../sufficientlysecure/keychain/EditKeyTest.java | 86 ++++++++++++++++++++++ .../keychain/provider/KeychainDatabase.java | 2 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java new file mode 100644 index 000000000..cee658f3e --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java @@ -0,0 +1,86 @@ +package org.sufficientlysecure.keychain; + + +import android.app.Activity; +import android.content.Intent; +import android.database.sqlite.SQLiteCursor; +import android.support.test.espresso.Espresso; +import android.support.test.espresso.action.ViewActions; +import android.support.test.espresso.matcher.ViewMatchers; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.LargeTest; +import android.widget.AdapterView; + +import org.hamcrest.CoreMatchers; +import org.hamcrest.collection.IsMapContaining; +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.sufficientlysecure.keychain.matcher.CustomMatchers; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.KeyListFragment.KeyListAdapter; +import org.sufficientlysecure.keychain.ui.MainActivity; +import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; + +import static android.support.test.espresso.Espresso.onData; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; +import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.anything; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; +import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; +import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId; + + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(AndroidJUnit4.class) +@LargeTest +public class EditKeyTest { + + @Rule + public final ActivityTestRule mActivity + = new ActivityTestRule(MainActivity.class) { + @Override + protected Intent getActivityIntent() { + Intent intent = super.getActivityIntent(); + intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true); + return intent; + } + }; + + @Test + public void test01Edit() throws Exception { + Activity activity = mActivity.getActivity(); + + new KeychainDatabase(activity).clearDatabase(); + + // import key for testing, get a stable initial state + importKeysFromResource(activity, "x.sec.asc"); + + // navigate to edit key dialog + onData(withKeyItemId(0x9D604D2F310716A3L)) + .inAdapterView(allOf(isAssignableFrom(AdapterView.class), + isDescendantOfA(withId(R.id.key_list_list)))) + .perform(click()); + onView(withId(R.id.menu_key_view_edit)).perform(click()); + + // no-op should yield snackbar + onView(withText(R.string.btn_save)).perform(click()); + checkSnackbar(Style.ERROR, R.string.msg_mf_error_noop); + + } + + +} 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; -- cgit v1.2.3 From 2d03965777e99782a0ee7a33e6b90805c79bb07e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jun 2015 17:54:53 +0200 Subject: instrument: finish symmetric text encryption test --- .../keychain/EncryptDecryptSymmetricTests.java | 32 +++++- .../keychain/EncryptDecryptTests.java | 2 +- .../sufficientlysecure/keychain/TestHelpers.java | 14 +++ .../keychain/matcher/DrawableMatcher.java | 112 +++++++++++++++++++++ 4 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/DrawableMatcher.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java index f668472b2..5141a1635 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java @@ -39,10 +39,14 @@ import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.typeText; import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.CoreMatchers.not; import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; +import static org.sufficientlysecure.keychain.TestHelpers.randomString; import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDrawer; +import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable; @FixMethodOrder(MethodSorters.NAME_ASCENDING) @@ -50,7 +54,7 @@ import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDr @LargeTest public class EncryptDecryptSymmetricTests { - public static final String PASSPHRASE = "fn9nf8wnaf"; + public static final String PASSPHRASE = randomString(5, 20); @Rule public final ActivityTestRule mActivity @@ -68,13 +72,14 @@ public class EncryptDecryptSymmetricTests { MainActivity activity = mActivity.getActivity(); + String text = randomString(10, 40); + // navigate to encrypt/decrypt onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); onView(ViewMatchers.withText(R.string.nav_encrypt_decrypt)).perform(click()); onView(withId(R.id.encrypt_text)).perform(click()); { - String text = "how much wood"; onView(withId(R.id.encrypt_text_text)).perform(typeText(text)); openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext()); @@ -99,7 +104,28 @@ public class EncryptDecryptSymmetricTests { pressBack(); onView(withId(R.id.decrypt_from_clipboard)).perform(click()); - // TODO fix thing, finish test + { + onView(withId(R.id.passphrase_passphrase)).perform(typeText(PASSPHRASE)); + onView(withText(R.string.btn_unlock)).perform(click()); + + onView(withId(R.id.decrypt_text_plaintext)).check(matches( + withText(text))); + + // TODO write generic status verifier + + onView(withId(R.id.result_encryption_text)).check(matches( + withText(R.string.decrypt_result_encrypted))); + onView(withId(R.id.result_signature_text)).check(matches( + withText(R.string.decrypt_result_no_signature))); + onView(withId(R.id.result_signature_layout)).check(matches( + not(isDisplayed()))); + + onView(withId(R.id.result_encryption_icon)).check(matches( + withDrawable(R.drawable.status_lock_closed_24dp))); + onView(withId(R.id.result_signature_icon)).check(matches( + withDrawable(R.drawable.status_signature_unknown_cutout_24dp))); + + } } diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java index 01a748172..e93a4c720 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java @@ -92,8 +92,8 @@ public class EncryptDecryptTests { { onView(withId(R.id.passphrase_passphrase)).perform(typeText("x")); - onView(withText(R.string.btn_unlock)).perform(click()); + } } diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java index 0adc6b264..da6988678 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java @@ -1,6 +1,8 @@ package org.sufficientlysecure.keychain; +import java.util.Random; + import android.content.Context; import android.support.annotation.StringRes; @@ -52,4 +54,16 @@ public class TestHelpers { } + public static String randomString(int min, int max) { + String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789!@#$%^&*()-_="; + Random r = new Random(); + StringBuilder passbuilder = new StringBuilder(); + // 5% chance for an empty string + for(int i = 0, j = r.nextInt(max)+min; i < j; i++) { + passbuilder.append(chars.charAt(r.nextInt(chars.length()))); + } + return passbuilder.toString(); + } + + } diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/DrawableMatcher.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/DrawableMatcher.java new file mode 100644 index 000000000..4996fe19a --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/DrawableMatcher.java @@ -0,0 +1,112 @@ +/** obtained from + * + * https://github.com/xrigau/droidcon-android-espresso/blob/master/app/src/instrumentTest/java/com/xrigau/droidcon/espresso/helper/DrawableMatcher.java + * + * license pending + */ +package org.sufficientlysecure.keychain.matcher; + + +import android.content.res.Resources; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + + +public class DrawableMatcher extends TypeSafeMatcher { + + private final int mResourceId; + private final boolean mIgnoreFilters; + + public DrawableMatcher(int resourceId, boolean ignoreFilters) { + super(View.class); + mResourceId = resourceId; + mIgnoreFilters = ignoreFilters; + } + + private String resourceName = null; + private Drawable expectedDrawable = null; + + @Override + public boolean matchesSafely(View target) { + if (expectedDrawable == null) { + loadDrawableFromResources(target.getResources()); + } + if (invalidExpectedDrawable()) { + return false; + } + + if (target instanceof ImageView) { + return hasImage((ImageView) target) || hasBackground(target); + } + if (target instanceof TextView) { + return hasCompoundDrawable((TextView) target) || hasBackground(target); + } + return hasBackground(target); + } + + private void loadDrawableFromResources(Resources resources) { + try { + expectedDrawable = resources.getDrawable(mResourceId); + resourceName = resources.getResourceEntryName(mResourceId); + } catch (Resources.NotFoundException ignored) { + // view could be from a context unaware of the resource id. + } + } + + private boolean invalidExpectedDrawable() { + return expectedDrawable == null; + } + + private boolean hasImage(ImageView target) { + return isSameDrawable(target.getDrawable()); + } + + private boolean hasCompoundDrawable(TextView target) { + for (Drawable drawable : target.getCompoundDrawables()) { + if (isSameDrawable(drawable)) { + return true; + } + } + return false; + } + + private boolean hasBackground(View target) { + return isSameDrawable(target.getBackground()); + } + + private boolean isSameDrawable(Drawable drawable) { + if (drawable == null) { + return false; + } + // if those are both bitmap drawables, compare their bitmaps (ignores color filters, which is what we want!) + if (mIgnoreFilters && drawable instanceof BitmapDrawable && expectedDrawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap().equals(((BitmapDrawable) expectedDrawable).getBitmap()); + } + return expectedDrawable.getConstantState().equals(drawable.getConstantState()); + } + + @Override + public void describeTo(Description description) { + description.appendText("with drawable from resource id: "); + description.appendValue(mResourceId); + if (resourceName != null) { + description.appendText("["); + description.appendText(resourceName); + description.appendText("]"); + } + } + + public static DrawableMatcher withDrawable(int resourceId, boolean ignoreFilters) { + return new DrawableMatcher(resourceId, ignoreFilters); + } + public static DrawableMatcher withDrawable(int resourceId) { + return new DrawableMatcher(resourceId, true); + } + +} -- cgit v1.2.3 From 84165c092e0ad10b543d763500a30fd12f1b95ee Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jun 2015 17:55:11 +0200 Subject: fix symmetric text decryption --- .../java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'OpenKeychain') 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 -- cgit v1.2.3 From b87a7372c9f5c5cca6cc90a5f4a95486b0766dda Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jun 2015 18:15:20 +0200 Subject: improve preview of ViewKeyActivity layout --- OpenKeychain/src/main/res/layout/view_key_activity.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/res/layout/view_key_activity.xml b/OpenKeychain/src/main/res/layout/view_key_activity.xml index 4dbd793ea..b3f4e721d 100644 --- a/OpenKeychain/src/main/res/layout/view_key_activity.xml +++ b/OpenKeychain/src/main/res/layout/view_key_activity.xml @@ -60,6 +60,7 @@ android:layout_marginRight="48dp" android:layout_marginEnd="48dp" android:text="" + tools:text="Alice Skywalker" android:textColor="@color/icons" android:textAppearance="?android:attr/textAppearanceLarge" android:layout_above="@+id/view_key_status" /> @@ -73,6 +74,7 @@ android:layout_marginRight="48dp" android:layout_marginEnd="48dp" android:text="" + tools:text="My Key" android:textColor="@color/tab_text" android:textAppearance="?android:attr/textAppearanceSmall" android:layout_above="@+id/toolbar2" /> @@ -95,6 +97,7 @@ android:layout_width="64dp" android:layout_height="64dp" android:visibility="invisible" + tools:visibility="visible" style="?android:attr/borderlessButtonStyle" android:src="@drawable/ic_action_encrypt_file_24dp" /> @@ -103,6 +106,7 @@ android:layout_width="64dp" android:layout_height="64dp" android:visibility="invisible" + tools:visibility="visible" style="?android:attr/borderlessButtonStyle" android:src="@drawable/ic_action_encrypt_text_24dp" /> @@ -111,6 +115,7 @@ android:layout_width="64dp" android:layout_height="64dp" android:visibility="invisible" + tools:visibility="visible" style="?android:attr/borderlessButtonStyle" android:src="@drawable/ic_nfc_white_24dp" /> @@ -120,6 +125,7 @@ android:id="@+id/view_key_status_image" android:layout_width="96dp" android:visibility="invisible" + tools:visibility="visible" android:src="@drawable/status_signature_unverified_cutout_96dp" android:layout_height="96dp" android:layout_above="@id/toolbar2" @@ -139,6 +145,7 @@ android:layout_height="wrap_content" android:clickable="true" android:foreground="?android:attr/selectableItemBackground" + tools:visibility="invisible" card_view:cardBackgroundColor="@android:color/white" card_view:cardElevation="2dp" card_view:cardUseCompatPadding="true" @@ -147,7 +154,8 @@ + android:layout_height="96dp" + /> @@ -189,6 +197,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="invisible" + tools:visibility="visible" android:elevation="4dp" fab:fab_icon="@drawable/ic_qrcode_white_24dp" fab:fab_colorNormal="@color/fab" -- cgit v1.2.3 From f677446d512a206a64e8fe9316baf42127a13435 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 16 Jun 2015 12:38:33 +0200 Subject: use KeyAdapter in KeySpinner --- .../compatibility/ClipboardReflection.java | 4 + .../keychain/ui/EncryptModeAsymmetricFragment.java | 2 +- .../keychain/ui/EncryptTextFragment.java | 2 +- .../keychain/ui/adapter/KeyAdapter.java | 69 +++++---- .../keychain/ui/widget/CertifyKeySpinner.java | 36 ++--- .../ui/widget/EncryptKeyCompletionView.java | 13 +- .../keychain/ui/widget/KeySpinner.java | 170 ++++++--------------- .../keychain/ui/widget/SignKeySpinner.java | 33 +--- .../src/main/res/layout/keyspinner_item.xml | 57 ------- .../src/main/res/layout/keyspinner_item_none.xml | 19 +++ 10 files changed, 146 insertions(+), 259 deletions(-) delete mode 100644 OpenKeychain/src/main/res/layout/keyspinner_item.xml create mode 100644 OpenKeychain/src/main/res/layout/keyspinner_item_none.xml (limited to 'OpenKeychain') 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/ui/EncryptModeAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java index ccc8bfa14..355c649e7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -167,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/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 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/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 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 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 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; } diff --git a/OpenKeychain/src/main/res/layout/keyspinner_item.xml b/OpenKeychain/src/main/res/layout/keyspinner_item.xml deleted file mode 100644 index eea81eba5..000000000 --- a/OpenKeychain/src/main/res/layout/keyspinner_item.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/keyspinner_item_none.xml b/OpenKeychain/src/main/res/layout/keyspinner_item_none.xml new file mode 100644 index 000000000..bd1d4ac11 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/keyspinner_item_none.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file -- cgit v1.2.3 From 7d371d8a39caebfa663ba26d491c5eacd904a19b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 16 Jun 2015 15:41:08 +0200 Subject: fix returned text of cleartext verify --- .../org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'OpenKeychain') 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 fd3d41f93..85f0ed3df 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -149,7 +149,7 @@ public class PgpDecryptVerify extends BaseOperation 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); @@ -833,7 +833,7 @@ public class PgpDecryptVerify extends BaseOperation * 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(); @@ -863,8 +863,9 @@ public class PgpDecryptVerify extends BaseOperation 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); -- cgit v1.2.3 From 3755bce9cee32041a236e0d4d66474eaf4e18386 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 16 Jun 2015 15:41:23 +0200 Subject: fix nullpointer in ViewKeyFragment --- .../main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain') 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 -- cgit v1.2.3 From e85982a41925288732de3eeb2970608a4e597f83 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 16 Jun 2015 15:41:46 +0200 Subject: nicer preview of decrypt_text_fragment layout --- OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml | 3 +++ 1 file changed, 3 insertions(+) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml b/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml index ea6be462f..cb20254ff 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml @@ -1,11 +1,13 @@ -- cgit v1.2.3 From d12d469714063746e98b6af7ba126f2fa75224a1 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 16 Jun 2015 15:42:21 +0200 Subject: some cleanup in instrumentation tests --- .../java/org/sufficientlysecure/keychain/EditKeyTest.java | 14 -------------- .../keychain/EncryptDecryptSymmetricTests.java | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java index cee658f3e..ebcc24c87 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java @@ -3,29 +3,18 @@ package org.sufficientlysecure.keychain; import android.app.Activity; import android.content.Intent; -import android.database.sqlite.SQLiteCursor; -import android.support.test.espresso.Espresso; -import android.support.test.espresso.action.ViewActions; -import android.support.test.espresso.matcher.ViewMatchers; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; import android.widget.AdapterView; -import org.hamcrest.CoreMatchers; -import org.hamcrest.collection.IsMapContaining; -import org.junit.Before; import org.junit.FixMethodOrder; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; -import org.sufficientlysecure.keychain.matcher.CustomMatchers; import org.sufficientlysecure.keychain.provider.KeychainDatabase; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.KeyListFragment.KeyListAdapter; import org.sufficientlysecure.keychain.ui.MainActivity; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import static android.support.test.espresso.Espresso.onData; @@ -36,9 +25,6 @@ import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.anything; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java index 5141a1635..081bef920 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java @@ -72,7 +72,7 @@ public class EncryptDecryptSymmetricTests { MainActivity activity = mActivity.getActivity(); - String text = randomString(10, 40); + String text = randomString(10, 30); // navigate to encrypt/decrypt onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); -- cgit v1.2.3 From d2cdfb34fa694886b231b76a3f66e6a18b6a3ed5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 16 Jun 2015 15:42:36 +0200 Subject: work on asymmetric operation instrumentation tests --- .../keychain/AsymmetricOperationTests.java | 233 +++++++++++++++++++++ .../keychain/EncryptDecryptTests.java | 101 --------- 2 files changed, 233 insertions(+), 101 deletions(-) create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java delete mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java new file mode 100644 index 000000000..59f6dbd79 --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain; + + +import android.app.Activity; +import android.content.Intent; +import android.support.test.espresso.matcher.ViewMatchers; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.LargeTest; +import android.widget.AdapterView; + +import org.hamcrest.CoreMatchers; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sufficientlysecure.keychain.ui.MainActivity; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; + +import static android.support.test.espresso.Espresso.onData; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.Espresso.pressBack; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.typeText; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; +import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.not; +import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; +import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; +import static org.sufficientlysecure.keychain.TestHelpers.randomString; +import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDrawer; +import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken; +import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId; +import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class AsymmetricOperationTests { + + @Rule + public final ActivityTestRule mActivity + = new ActivityTestRule(MainActivity.class) { + @Override + protected Intent getActivityIntent() { + Intent intent = super.getActivityIntent(); + intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true); + return intent; + } + }; + + @Before + public void setUp() throws Exception { + Activity activity = mActivity.getActivity(); + + // import these two, make sure they're there + importKeysFromResource(activity, "x.sec.asc"); + } + + @Test + public void testTextEncryptDecryptFromToken() throws Exception { + + // navigate to 'encrypt text' + onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); + onView(withText(R.string.nav_encrypt_decrypt)).perform(click()); + onView(withId(R.id.encrypt_text)).perform(click()); + + String cleartext = randomString(10, 30); + + { // encrypt + // TODO instrument this (difficult because of TokenCompleteView's async implementation) + onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L)); + + onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext)); + + onView(withId(R.id.encrypt_copy)).perform(click()); + } + + // go to decrypt from clipboard view + pressBack(); + onView(withId(R.id.decrypt_from_clipboard)).perform(click()); + + { // decrypt + onView(withId(R.id.passphrase_passphrase)).perform(typeText("x")); + onView(withText(R.string.btn_unlock)).perform(click()); + + onView(withId(R.id.decrypt_text_plaintext)).check(matches( + withText(cleartext))); + + onView(withId(R.id.result_encryption_text)).check(matches( + withText(R.string.decrypt_result_encrypted))); + onView(withId(R.id.result_signature_text)).check(matches( + withText(R.string.decrypt_result_no_signature))); + onView(withId(R.id.result_signature_layout)).check(matches( + not(isDisplayed()))); + + onView(withId(R.id.result_encryption_icon)).check(matches( + withDrawable(R.drawable.status_lock_closed_24dp))); + onView(withId(R.id.result_signature_icon)).check(matches( + withDrawable(R.drawable.status_signature_unknown_cutout_24dp))); + } + + } + + @Test + public void testTextEncryptDecryptFromKeyView() throws Exception { + + String cleartext = randomString(10, 30); + + { // encrypt + + // navigate to edit key dialog + onData(withKeyItemId(0x9D604D2F310716A3L)) + .inAdapterView(allOf(isAssignableFrom(AdapterView.class), + isDescendantOfA(withId(R.id.key_list_list)))) + .perform(click()); + onView(withId(R.id.view_key_action_encrypt_text)).perform(click()); + + onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext)); + + onView(withId(R.id.encrypt_copy)).perform(click()); + } + + // go to decrypt from clipboard view + pressBack(); + pressBack(); + + onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); + onView(withText(R.string.nav_encrypt_decrypt)).perform(click()); + onView(withId(R.id.decrypt_from_clipboard)).perform(click()); + + { // decrypt + + onView(withId(R.id.passphrase_passphrase)).perform(typeText("x")); + onView(withText(R.string.btn_unlock)).perform(click()); + + onView(withId(R.id.decrypt_text_plaintext)).check(matches( + withText(cleartext))); + + onView(withId(R.id.result_encryption_text)).check(matches( + withText(R.string.decrypt_result_encrypted))); + onView(withId(R.id.result_signature_text)).check(matches( + withText(R.string.decrypt_result_no_signature))); + onView(withId(R.id.result_signature_layout)).check(matches( + not(isDisplayed()))); + + onView(withId(R.id.result_encryption_icon)).check(matches( + withDrawable(R.drawable.status_lock_closed_24dp))); + onView(withId(R.id.result_signature_icon)).check(matches( + withDrawable(R.drawable.status_signature_unknown_cutout_24dp))); + + } + + } + + @Test + public void testSignVerify() throws Exception { + + String cleartext = randomString(10, 30); + + // navigate to 'encrypt text' + onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); + onView(withText(R.string.nav_encrypt_decrypt)).perform(click()); + onView(withId(R.id.encrypt_text)).perform(click()); + + { // sign + + // navigate to edit key dialog + onView(withId(R.id.sign)).perform(click()); + onData(withKeyItemId(0x9D604D2F310716A3L)) + .inAdapterView(isAssignableFrom(AdapterView.class)) + .perform(click()); + + onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext)); + + onView(withId(R.id.encrypt_copy)).perform(click()); + + onView(withId(R.id.passphrase_passphrase)).perform(typeText("x")); + onView(withText(R.string.btn_unlock)).perform(click()); + + checkSnackbar(Style.OK, R.string.msg_se_success); + + } + + // go to decrypt from clipboard view + pressBack(); + + onView(withId(R.id.decrypt_from_clipboard)).perform(click()); + + { // decrypt + + onView(withId(R.id.decrypt_text_plaintext)).check(matches( + // startsWith because there may be extra newlines + withText(CoreMatchers.startsWith(cleartext)))); + + onView(withId(R.id.result_encryption_text)).check(matches( + withText(R.string.decrypt_result_not_encrypted))); + onView(withId(R.id.result_signature_text)).check(matches( + withText(R.string.decrypt_result_signature_secret))); + onView(withId(R.id.result_signature_layout)).check(matches( + isDisplayed())); + + onView(withId(R.id.result_encryption_icon)).check(matches( + withDrawable(R.drawable.status_lock_open_24dp))); + onView(withId(R.id.result_signature_icon)).check(matches( + withDrawable(R.drawable.status_signature_verified_cutout_24dp))); + + } + + } + +} diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java deleted file mode 100644 index e93a4c720..000000000 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2015 Dominik Schürmann - * - * 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 . - */ - -package org.sufficientlysecure.keychain; - - -import android.app.Activity; -import android.content.Intent; -import android.support.test.espresso.matcher.ViewMatchers; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.LargeTest; - -import org.junit.Before; -import org.junit.FixMethodOrder; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.MethodSorters; -import org.sufficientlysecure.keychain.ui.MainActivity; - -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.Espresso.pressBack; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.action.ViewActions.typeText; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; -import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDrawer; -import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken; - - -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@RunWith(AndroidJUnit4.class) -@LargeTest -public class EncryptDecryptTests { - - @Rule - public final ActivityTestRule mActivity - = new ActivityTestRule(MainActivity.class) { - @Override - protected Intent getActivityIntent() { - Intent intent = super.getActivityIntent(); - intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true); - return intent; - } - }; - - @Before - public void setUp() throws Exception { - Activity activity = mActivity.getActivity(); - - // import these two, make sure they're there - importKeysFromResource(activity, "x.sec.asc"); - } - - @Test - public void testTextEncryptDecrypt() throws Exception { - - // navigate to encrypt/decrypt - onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); - onView(ViewMatchers.withText(R.string.nav_encrypt_decrypt)).perform(click()); - onView(withId(R.id.encrypt_text)).perform(click()); - - { - // TODO instrument this (difficult because of TokenCompleteView's async implementation) - onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L)); - - String text = "how much wood"; - onView(withId(R.id.encrypt_text_text)).perform(typeText(text)); - - onView(withId(R.id.encrypt_copy)).perform(click()); - } - - // go to decrypt from clipboard view - pressBack(); - onView(withId(R.id.decrypt_from_clipboard)).perform(click()); - - { - onView(withId(R.id.passphrase_passphrase)).perform(typeText("x")); - onView(withText(R.string.btn_unlock)).perform(click()); - - } - - } - -} -- cgit v1.2.3 From 04e9137b663555c491be90be31de5104495782cc Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 16 Jun 2015 16:20:09 +0200 Subject: instrument: use contrib drawer methods, respect passphrase cache --- .../keychain/AsymmetricOperationTests.java | 35 +++++++++++++++++--- .../keychain/EncryptDecryptSymmetricTests.java | 4 +-- .../keychain/actions/CustomActions.java | 38 ---------------------- .../keychain/service/PassphraseCacheService.java | 8 +++++ 4 files changed, 40 insertions(+), 45 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java index 59f6dbd79..3a4114fbe 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java @@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain; import android.app.Activity; import android.content.Intent; -import android.support.test.espresso.matcher.ViewMatchers; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; @@ -31,6 +30,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.MainActivity; import org.sufficientlysecure.keychain.ui.util.Notify.Style; @@ -40,6 +40,7 @@ import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.typeText; import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.contrib.DrawerActions.openDrawer; import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; @@ -50,7 +51,6 @@ import static org.hamcrest.CoreMatchers.not; import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; import static org.sufficientlysecure.keychain.TestHelpers.randomString; -import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDrawer; import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId; import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable; @@ -76,13 +76,16 @@ public class AsymmetricOperationTests { // import these two, make sure they're there importKeysFromResource(activity, "x.sec.asc"); + + // make sure no passphrases are cached + PassphraseCacheService.clearCachedPassphrases(activity); } @Test public void testTextEncryptDecryptFromToken() throws Exception { // navigate to 'encrypt text' - onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); + openDrawer(R.id.drawer_layout); onView(withText(R.string.nav_encrypt_decrypt)).perform(click()); onView(withId(R.id.encrypt_text)).perform(click()); @@ -146,7 +149,7 @@ public class AsymmetricOperationTests { pressBack(); pressBack(); - onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); + openDrawer(R.id.drawer_layout); onView(withText(R.string.nav_encrypt_decrypt)).perform(click()); onView(withId(R.id.decrypt_from_clipboard)).perform(click()); @@ -172,6 +175,28 @@ public class AsymmetricOperationTests { } + pressBack(); + onView(withId(R.id.decrypt_from_clipboard)).perform(click()); + + { // decrypt again, passphrase should be cached + + onView(withId(R.id.decrypt_text_plaintext)).check(matches( + withText(cleartext))); + + onView(withId(R.id.result_encryption_text)).check(matches( + withText(R.string.decrypt_result_encrypted))); + onView(withId(R.id.result_signature_text)).check(matches( + withText(R.string.decrypt_result_no_signature))); + onView(withId(R.id.result_signature_layout)).check(matches( + not(isDisplayed()))); + + onView(withId(R.id.result_encryption_icon)).check(matches( + withDrawable(R.drawable.status_lock_closed_24dp))); + onView(withId(R.id.result_signature_icon)).check(matches( + withDrawable(R.drawable.status_signature_unknown_cutout_24dp))); + + } + } @Test @@ -180,7 +205,7 @@ public class AsymmetricOperationTests { String cleartext = randomString(10, 30); // navigate to 'encrypt text' - onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); + openDrawer(R.id.drawer_layout); onView(withText(R.string.nav_encrypt_decrypt)).perform(click()); onView(withId(R.id.encrypt_text)).perform(click()); diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java index 081bef920..919bb710a 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java @@ -39,13 +39,13 @@ import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.typeText; import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.contrib.DrawerActions.openDrawer; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.CoreMatchers.not; import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; import static org.sufficientlysecure.keychain.TestHelpers.randomString; -import static org.sufficientlysecure.keychain.actions.CustomActions.actionOpenDrawer; import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable; @@ -75,7 +75,7 @@ public class EncryptDecryptSymmetricTests { String text = randomString(10, 30); // navigate to encrypt/decrypt - onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer()); + openDrawer(R.id.drawer_layout); onView(ViewMatchers.withText(R.string.nav_encrypt_decrypt)).perform(click()); onView(withId(R.id.encrypt_text)).perform(click()); diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java index ff9384247..feb95403b 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java @@ -61,42 +61,4 @@ public abstract class CustomActions { }; } - public static ViewAction actionOpenDrawer() { - return new ViewAction() { - @Override - public Matcher getConstraints() { - return ViewMatchers.isAssignableFrom(DrawerLayout.class); - } - - @Override - public String getDescription() { - return "open drawer"; - } - - @Override - public void perform(UiController uiController, View view) { - ((DrawerLayout) view).openDrawer(GravityCompat.START); - } - }; - } - - public static ViewAction actionCloseDrawer() { - return new ViewAction() { - @Override - public Matcher getConstraints() { - return ViewMatchers.isAssignableFrom(DrawerLayout.class); - } - - @Override - public String getDescription() { - return "close drawer"; - } - - @Override - public void perform(UiController uiController, View view) { - ((DrawerLayout) view).closeDrawer(GravityCompat.START); - } - }; - } - } \ No newline at end of file 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 -- cgit v1.2.3 From 7b416d7d7dc3a98d5a970c0e0411327c9d16da02 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 16 Jun 2015 18:11:40 +0200 Subject: instrument: test EncryptKeyCompletionView --- .../keychain/AsymmetricOperationTests.java | 6 +- .../keychain/EncryptKeyCompletionViewTest.java | 89 ++++++++++++++++++++++ .../keychain/matcher/CustomMatchers.java | 24 +++++- 3 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptKeyCompletionViewTest.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java index 3a4114fbe..8158bdcd2 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java @@ -92,7 +92,8 @@ public class AsymmetricOperationTests { String cleartext = randomString(10, 30); { // encrypt - // TODO instrument this (difficult because of TokenCompleteView's async implementation) + + // the EncryptKeyCompletionView is tested individually onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L)); onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext)); @@ -211,6 +212,9 @@ public class AsymmetricOperationTests { { // sign + onView(withId(R.id.encrypt_copy)).perform(click()); + checkSnackbar(Style.ERROR, R.string.error_empty_text); + // navigate to edit key dialog onView(withId(R.id.sign)).perform(click()); onData(withKeyItemId(0x9D604D2F310716A3L)) diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptKeyCompletionViewTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptKeyCompletionViewTest.java new file mode 100644 index 000000000..1e9bebcee --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptKeyCompletionViewTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain; + + +import android.app.Activity; +import android.content.Intent; +import android.support.test.espresso.action.ViewActions; +import android.support.test.espresso.matcher.RootMatchers; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.KeyEvent; +import android.widget.AdapterView; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sufficientlysecure.keychain.ui.EncryptTextActivity; + +import static android.support.test.espresso.Espresso.onData; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.typeText; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; +import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static org.hamcrest.CoreMatchers.allOf; +import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; +import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken; +import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId; +import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyToken; + + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class EncryptKeyCompletionViewTest { + + @Rule + public final ActivityTestRule mActivity + = new ActivityTestRule<>(EncryptTextActivity.class); + + @Test + public void testTextEncryptDecryptFromToken() throws Exception { + + Intent intent = new Intent(); + intent.putExtra(EncryptTextActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[] { 0x9D604D2F310716A3L }); + Activity activity = mActivity.launchActivity(intent); + + // import these two, make sure they're there + importKeysFromResource(activity, "x.sec.asc"); + + // check if the element passed in from intent + onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L))); + onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL)); + + // type X, select from list, check if it's there + onView(withId(R.id.recipient_list)).perform(typeText("x")); + onData(withKeyItemId(0x9D604D2F310716A3L)).inRoot(RootMatchers.isPlatformPopup()) + .inAdapterView(allOf(isAssignableFrom(AdapterView.class), + hasDescendant(withId(R.id.key_list_item_name)))).perform(click()); + onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L))); + onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL)); + onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL)); + + // add directly, check if it's there + onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L)); + onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L))); + onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL)); + + } + +} diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java index c023f6411..d2e7fcdf1 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java @@ -3,15 +3,14 @@ package org.sufficientlysecure.keychain.matcher; import android.support.annotation.ColorRes; import android.support.test.espresso.matcher.BoundedMatcher; -import android.support.test.internal.util.Checks; import android.view.View; import com.nispok.snackbar.Snackbar; import org.hamcrest.Description; import org.hamcrest.Matcher; -import org.hamcrest.Matchers; -import org.sufficientlysecure.keychain.ui.KeyListFragment.KeyListAdapter; +import org.sufficientlysecure.keychain.EncryptKeyCompletionViewTest; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; +import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView; import static android.support.test.internal.util.Checks.checkNotNull; @@ -45,4 +44,23 @@ public abstract class CustomMatchers { }; } + public static Matcher withKeyToken(@ColorRes final long keyId) { + return new BoundedMatcher(EncryptKeyCompletionView.class) { + public void describeTo(Description description) { + description.appendText("with key id token: " + keyId); + } + + @Override + public boolean matchesSafely(EncryptKeyCompletionView tokenView) { + for (Object object : tokenView.getObjects()) { + if (object instanceof KeyItem && ((KeyItem) object).mKeyId == keyId) { + return true; + } + } + return false; + } + }; + } + + } -- cgit v1.2.3 From 0c20d408637e2b03990ea7e5c0de31d3f964cf9f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 16 Jun 2015 18:32:03 +0200 Subject: instrument: import public keys as public --- .../androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java index da6988678..904dcfc8a 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java @@ -48,7 +48,7 @@ public class TestHelpers { if (ring.isSecret()) { helper.saveSecretKeyRing(ring, new ProgressScaler()); } else { - helper.saveSecretKeyRing(ring, new ProgressScaler()); + helper.savePublicKeyRing(ring, new ProgressScaler()); } } -- cgit v1.2.3 From 0d61221c5f85d0a08025a5b87fa8dfb11b961e9e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 17 Jun 2015 01:30:33 +0200 Subject: instrument: license stuff --- .../keychain/AsymmetricOperationTests.java | 2 +- .../sufficientlysecure/keychain/EditKeyTest.java | 17 +++++++++++++++++ .../keychain/EncryptDecryptSymmetricTests.java | 2 +- .../keychain/EncryptKeyCompletionViewTest.java | 2 +- .../sufficientlysecure/keychain/TestHelpers.java | 17 +++++++++++++++++ .../keychain/actions/CustomActions.java | 18 ++++++++++++++++++ .../keychain/matcher/CustomMatchers.java | 18 ++++++++++++++++++ .../keychain/matcher/DrawableMatcher.java | 22 +++++++++++++++++++--- .../keychain/matcher/EditTextMatchers.java | 2 +- 9 files changed, 93 insertions(+), 7 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java index 8158bdcd2..15de32c8a 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AsymmetricOperationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser * * 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 diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java index ebcc24c87..6773a7b2d 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EditKeyTest.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2015 Vincent Breitmoser + * + * 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 . + */ + package org.sufficientlysecure.keychain; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java index 919bb710a..f07566755 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptDecryptSymmetricTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser * * 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 diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptKeyCompletionViewTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptKeyCompletionViewTest.java index 1e9bebcee..40cdbd4eb 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptKeyCompletionViewTest.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/EncryptKeyCompletionViewTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser * * 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 diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java index 904dcfc8a..4c058940b 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2015 Vincent Breitmoser + * + * 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 . + */ + package org.sufficientlysecure.keychain; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java index feb95403b..75197ac9e 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2015 Vincent Breitmoser + * + * 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 . + * + */ + package org.sufficientlysecure.keychain.actions; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java index d2e7fcdf1..1db902c4c 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2015 Vincent Breitmoser + * + * 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 . + * + */ + package org.sufficientlysecure.keychain.matcher; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/DrawableMatcher.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/DrawableMatcher.java index 4996fe19a..761fb8e0b 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/DrawableMatcher.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/DrawableMatcher.java @@ -1,9 +1,25 @@ -/** obtained from +/* + * Copyright (C) 2015 Xavi Rigau + * Copyright (C) 2015 Vincent Breitmoser * - * https://github.com/xrigau/droidcon-android-espresso/blob/master/app/src/instrumentTest/java/com/xrigau/droidcon/espresso/helper/DrawableMatcher.java + * 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 . + * + * From the droidcon anroid espresso repository. + * https://github.com/xrigau/droidcon-android-espresso/ * - * license pending */ + package org.sufficientlysecure.keychain.matcher; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java index 7f2a7953b..a1df7912f 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser * * 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 -- cgit v1.2.3 From 8e60ccb650ba4af516687558fe365540a92a1630 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 17 Jun 2015 04:27:03 +0200 Subject: workaround for coverage bug (for now!) see android bug report https://code.google.com/p/android/issues/detail?id=170607 --- OpenKeychain/build.gradle | 7 ++++-- .../keychain/JacocoWorkaroundJUnitRunner.java | 29 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/JacocoWorkaroundJUnitRunner.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index f85678b67..491926a4e 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -114,7 +114,7 @@ android { versionCode 32300 versionName "3.2.3" applicationId "org.sufficientlysecure.keychain" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "org.sufficientlysecure.keychain.JacocoWorkaroundJUnitRunner" } compileOptions { @@ -217,7 +217,10 @@ task jacocoTestReport(type:JacocoReport) { "${buildDir}/generated/source/buildConfig/debug", "${buildDir}/generated/source/r/debug" ]) - executionData = files("${buildDir}/jacoco/testDebug.exec") + executionData = files([ + "${buildDir}/jacoco/testDebug.exec", + "${buildDir}/outputs/code-coverage/connected/coverage.ec" + ]) reports { xml.enabled = true diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/JacocoWorkaroundJUnitRunner.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/JacocoWorkaroundJUnitRunner.java new file mode 100644 index 000000000..b310ed5b8 --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/JacocoWorkaroundJUnitRunner.java @@ -0,0 +1,29 @@ +package org.sufficientlysecure.keychain; + + +import java.lang.reflect.Method; + +import android.os.Bundle; +import android.support.test.runner.AndroidJUnitRunner; + + +public class JacocoWorkaroundJUnitRunner extends AndroidJUnitRunner { + static { + System.setProperty("jacoco-agent.destfile", "/data/data/" + + BuildConfig.APPLICATION_ID + "/coverage.ec"); + } + + @Override + public void finish(int resultCode, Bundle results) { + try { + Class rt = Class.forName("org.jacoco.agent.rt.RT"); + Method getAgent = rt.getMethod("getAgent"); + Method dump = getAgent.getReturnType().getMethod("dump", boolean.class); + Object agent = getAgent.invoke(null); + dump.invoke(agent, false); + } catch (Exception e) { + e.printStackTrace(); + } + super.finish(resultCode, results); + } +} \ No newline at end of file -- cgit v1.2.3 From 5e771172329e59e3e0b54581d489cb5880388260 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 17 Jun 2015 04:27:42 +0200 Subject: fix filesize reporting in decrypt operation --- .../java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'OpenKeychain') 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 85f0ed3df..e3059defb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -757,6 +757,12 @@ public class PgpDecryptVerify extends BaseOperation // TODO: slow annealing to fake a progress? } + metadata = new OpenPgpMetadata( + originalFilename, + mimeType, + literalData.getModificationTime().getTime(), + alreadyWritten); + if (signature != null) { updateProgress(R.string.progress_verifying_signature, 90, 100); log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent); -- cgit v1.2.3 From e3ed2c8e7224ae8efd1f4a1ff520ed3965af840c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 17 Jun 2015 05:28:40 +0200 Subject: just force commit ui test coverage --- .../build/outputs/code-coverage/connected/coverage.ec | Bin 0 -> 20285 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 OpenKeychain/build/outputs/code-coverage/connected/coverage.ec (limited to 'OpenKeychain') diff --git a/OpenKeychain/build/outputs/code-coverage/connected/coverage.ec b/OpenKeychain/build/outputs/code-coverage/connected/coverage.ec new file mode 100644 index 000000000..cd6aee896 Binary files /dev/null and b/OpenKeychain/build/outputs/code-coverage/connected/coverage.ec differ -- cgit v1.2.3 From 04d2b6a5076a1a7264687999152f8c24ece773ab Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 17 Jun 2015 18:30:58 +0200 Subject: use regular runner for most cases --- OpenKeychain/build.gradle | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 491926a4e..e2d1dd8c8 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -114,7 +114,13 @@ android { versionCode 32300 versionName "3.2.3" applicationId "org.sufficientlysecure.keychain" - testInstrumentationRunner "org.sufficientlysecure.keychain.JacocoWorkaroundJUnitRunner" + // the androidjunitrunner is broken regarding coverage, see here: + // https://code.google.com/p/android/issues/detail?id=170607 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + // this workaround runner fixes the coverage problem, BUT doesn't work + // with android studio single test execution. use it to generate coverage + // data, but keep the other one otherwis + // testInstrumentationRunner "org.sufficientlysecure.keychain.JacocoWorkaroundJUnitRunner" } compileOptions { -- cgit v1.2.3