From d097131a3dcdf806608f43e49e0be96cf34f5c64 Mon Sep 17 00:00:00 2001 From: Daniel Ramos Date: Tue, 14 Apr 2015 03:40:59 +0100 Subject: [PATCH 01/71] -Improved email sanity verifications when adding emails when creating a new Key -fixed a few NFC crashes -fixed a few instances when showing the keyboard would crash the Activity -fixed a case where adding new emails would crash the app if the user went back and forth between the Add Email fragment and the Add Name fragment. --- .../keychain/ui/CreateKeyEmailFragment.java | 95 ++++++++++++++----- .../keychain/ui/base/BaseNfcActivity.java | 6 +- .../ui/dialog/AddEmailDialogFragment.java | 8 +- .../src/main/res/layout/add_email_dialog.xml | 1 + .../res/layout/create_key_email_fragment.xml | 1 + OpenKeychain/src/main/res/values/strings.xml | 1 + 6 files changed, 82 insertions(+), 30 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 85e2f8e9d..26c307957 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -27,6 +26,7 @@ import android.support.v4.app.Fragment; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Patterns; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -44,6 +44,7 @@ import org.sufficientlysecure.keychain.ui.widget.EmailEditText; import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; public class CreateKeyEmailFragment extends Fragment { @@ -73,14 +74,13 @@ public class CreateKeyEmailFragment extends Fragment { * Checks if text of given EditText is not empty. If it is empty an error is * set and the EditText gets the focus. * - * @param context * @param editText * @return true if EditText is not empty */ - private static boolean isEditTextNotEmpty(Context context, EditText editText) { + private boolean isMainEmailValid(EditText editText) { boolean output = true; - if (editText.getText().length() == 0) { - editText.setError(context.getString(R.string.create_key_empty)); + if (!checkEmail(editText.getText().toString(), false)) { + editText.setError(getString(R.string.create_key_empty)); editText.requestFocus(); output = false; } else { @@ -125,7 +125,7 @@ public class CreateKeyEmailFragment extends Fragment { // initial values if (mAdditionalEmailModels == null) { mAdditionalEmailModels = new ArrayList<>(); - if (mCreateKeyActivity.mAdditionalEmails != null) { + if (mCreateKeyActivity.mAdditionalEmails != null && mEmailAdapter != null) { mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); } } @@ -144,6 +144,65 @@ public class CreateKeyEmailFragment extends Fragment { return view; } + /** + * Checks if a given email is valid + * + * @param email + * @param additionalEmail + * @return + */ + private boolean checkEmail(String email, boolean additionalEmail) { + //check for email format or if the user did any input + if (!isEmailFormatValid(email)) { + Notify.create(getActivity(), + getString(R.string.create_key_email_invalid_email), + Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + return false; + } + + //check for duplicated emails + if (!additionalEmail && isEmailDuplicatedInsideAdapter(email) || additionalEmail && + mEmailEdit.getText().length() > 0 && email.equals(mEmailEdit.getText().toString())) { + Notify.create(getActivity(), + getString(R.string.create_key_email_already_exists_text), + Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + return false; + } + + return true; + } + + /** + * Checks the email format + * Uses the default Android Email Pattern + * + * @param email + * @return + */ + private boolean isEmailFormatValid(String email) { + Pattern emailPattern = Patterns.EMAIL_ADDRESS; + + //check for email format or if the user did any input + return !(email.length() == 0 || !emailPattern.matcher(email).matches()); + } + + /** + * Checks for duplicated emails inside the additional email adapter. + * + * @param email + * @return + */ + private boolean isEmailDuplicatedInsideAdapter(String email) { + //check for duplicated emails inside the adapter + for (EmailAdapter.ViewModel model : mAdditionalEmailModels) { + if (email.equals(model.email)) { + return true; + } + } + + return false; + } + private void addEmail() { Handler returnHandler = new Handler() { @Override @@ -152,26 +211,10 @@ public class CreateKeyEmailFragment extends Fragment { Bundle data = message.getData(); String email = data.getString(AddEmailDialogFragment.MESSAGE_DATA_EMAIL); - - if (email.length() > 0 && mEmailEdit.getText().length() > 0 && - email.equals(mEmailEdit.getText().toString())) { - Notify.create(getActivity(), - getString(R.string.create_key_email_already_exists_text), - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); - return; + if (checkEmail(email, true)) { + // add new user id + mEmailAdapter.add(email); } - //check for duplicated emails inside the adapter - for (EmailAdapter.ViewModel model : mAdditionalEmailModels) { - if (email.equals(model.email)) { - Notify.create(getActivity(), - getString(R.string.create_key_email_already_exists_text), - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); - return; - } - } - - // add new user id - mEmailAdapter.add(email); } } }; @@ -191,7 +234,7 @@ public class CreateKeyEmailFragment extends Fragment { } private void nextClicked() { - if (isEditTextNotEmpty(getActivity(), mEmailEdit)) { + if (isMainEmailValid(mEmailEdit)) { // save state mCreateKeyActivity.mEmail = mEmailEdit.getText().toString(); mCreateKeyActivity.mAdditionalEmails = getAdditionalEmails(); 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 9b10ccdb1..4f6d5807e 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 @@ -451,6 +451,8 @@ public abstract class BaseNfcActivity extends BaseActivity { */ public void enableNfcForegroundDispatch() { mNfcAdapter = NfcAdapter.getDefaultAdapter(this); + if(mNfcAdapter == null) return; + Intent nfcI = new Intent(this, getClass()) .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT); @@ -472,7 +474,9 @@ public abstract class BaseNfcActivity extends BaseActivity { * Disable foreground dispatch in onPause! */ public void disableNfcForegroundDispatch() { - mNfcAdapter.disableForegroundDispatch(this); + if(mNfcAdapter != null) { + mNfcAdapter.disableForegroundDispatch(this); + } Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java index 5d5ca533e..5b91b9d37 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java @@ -112,9 +112,11 @@ public class AddEmailDialogFragment extends DialogFragment implements OnEditorAc mEmail.post(new Runnable() { @Override public void run() { - InputMethodManager imm = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mEmail, InputMethodManager.SHOW_IMPLICIT); + if(getActivity() != null) { + InputMethodManager imm = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mEmail, InputMethodManager.SHOW_IMPLICIT); + } } }); } diff --git a/OpenKeychain/src/main/res/layout/add_email_dialog.xml b/OpenKeychain/src/main/res/layout/add_email_dialog.xml index 68d895145..2de657a40 100644 --- a/OpenKeychain/src/main/res/layout/add_email_dialog.xml +++ b/OpenKeychain/src/main/res/layout/add_email_dialog.xml @@ -21,6 +21,7 @@ android:layout_marginTop="16dp" android:hint="@string/label_email" android:imeOptions="actionNext" + android:inputType="textEmailAddress" android:textAppearance="?android:attr/textAppearanceMedium" /> diff --git a/OpenKeychain/src/main/res/layout/create_key_email_fragment.xml b/OpenKeychain/src/main/res/layout/create_key_email_fragment.xml index 17cfe54ac..7b9cffec4 100644 --- a/OpenKeychain/src/main/res/layout/create_key_email_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_key_email_fragment.xml @@ -32,6 +32,7 @@ android:layout_marginTop="16dp" android:layout_marginBottom="8dp" android:imeOptions="actionNext" + android:inputType="textEmailAddress" android:hint="@string/label_email" android:ems="10" /> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index c1e3b51f9..c1ff5a1d1 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -631,6 +631,7 @@ "Add email address" "Additional email addresses are also associated to this key and can be used for secure communication." "Email address has already been added" + "Email address format is invalid" "Revoked: Key must not be used anymore!" From d9cabf8dc4a4de0153ab6a07919ae8d7614cfc9d Mon Sep 17 00:00:00 2001 From: Daniel Ramos Date: Fri, 24 Apr 2015 03:35:30 +0100 Subject: [PATCH 02/71] -Fixed a crash while recreating CreateKeyEmailFragment -Email data is properly restored while rotating the Activity. -Added necessary logic to guarantee that an email is actually valid before continuing, there are verifications for empty, duplicated and invalid formatted emails for both additional and main email. --- .../keychain/ui/CreateKeyEmailFragment.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 26c307957..8fdfb35cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -47,16 +47,13 @@ import java.util.List; import java.util.regex.Pattern; public class CreateKeyEmailFragment extends Fragment { - - CreateKeyActivity mCreateKeyActivity; - EmailEditText mEmailEdit; - RecyclerView mEmailsRecyclerView; - View mBackButton; - View mNextButton; - - ArrayList mAdditionalEmailModels; - - EmailAdapter mEmailAdapter; + private CreateKeyActivity mCreateKeyActivity; + private EmailEditText mEmailEdit; + private RecyclerView mEmailsRecyclerView; + private View mBackButton; + private View mNextButton; + private ArrayList mAdditionalEmailModels; + private EmailAdapter mEmailAdapter; /** * Creates new instance of this fragment @@ -125,9 +122,6 @@ public class CreateKeyEmailFragment extends Fragment { // initial values if (mAdditionalEmailModels == null) { mAdditionalEmailModels = new ArrayList<>(); - if (mCreateKeyActivity.mAdditionalEmails != null && mEmailAdapter != null) { - mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); - } } if (mEmailAdapter == null) { @@ -137,6 +131,10 @@ public class CreateKeyEmailFragment extends Fragment { addEmail(); } }); + + if (mCreateKeyActivity.mAdditionalEmails != null) { + mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); + } } mEmailsRecyclerView.setAdapter(mEmailAdapter); @@ -203,6 +201,9 @@ public class CreateKeyEmailFragment extends Fragment { return false; } + /** + * Displays a dialog fragment for the user to input a valid email. + */ private void addEmail() { Handler returnHandler = new Handler() { @Override @@ -218,12 +219,11 @@ public class CreateKeyEmailFragment extends Fragment { } } }; - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); + Messenger messenger = new Messenger(returnHandler); AddEmailDialogFragment addEmailDialog = AddEmailDialogFragment.newInstance(messenger); - + addEmailDialog.setTargetFragment(this, -1); addEmailDialog.show(getActivity().getSupportFragmentManager(), "addEmailDialog"); } From ef52a3319a5931c46d5ae5e83f96c503782c805a Mon Sep 17 00:00:00 2001 From: Manoj Khanna Date: Fri, 24 Apr 2015 00:06:35 +0530 Subject: [PATCH 03/71] Espresso test for CreateKeyActivity --- OpenKeychain/build.gradle | 12 ++ .../keychain/CreateKeyActivityTest.java | 194 ++++++++++++++++++ .../keychain/matcher/EditTextMatchers.java | 74 +++++++ .../keychain/ui/CreateKeyEmailFragment.java | 11 +- 4 files changed, 286 insertions(+), 5 deletions(-) create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java create mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index c47e650cb..cc5e01aca 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -11,6 +11,12 @@ dependencies { compile 'com.android.support:recyclerview-v7:22.0.0' compile 'com.android.support:cardview-v7:22.0.0' + // UI testing libs + 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' + // JCenter etc. compile 'com.eftimoff:android-patternview:1.0.1@aar' compile 'com.journeyapps:zxing-android-embedded:2.3.0@aar' @@ -85,6 +91,8 @@ android { defaultConfig { minSdkVersion 15 targetSdkVersion 22 + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } compileOptions { @@ -135,6 +143,10 @@ android { dexOptions { preDexLibraries = false } + + packagingOptions { + exclude 'LICENSE.txt' + } } // NOTE: This disables Lint! diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java new file mode 100644 index 000000000..c3741fdef --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java @@ -0,0 +1,194 @@ +/* + * 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.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +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; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.swipeLeft; +import static android.support.test.espresso.action.ViewActions.typeText; +import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.RootMatchers.isDialog; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; +import static android.support.test.espresso.matcher.ViewMatchers.hasSibling; +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.Matchers.allOf; +import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withError; +import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withTransformationMethod; + +@RunWith(AndroidJUnit4.class) +public class CreateKeyActivityTest { + + 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); + + @Test + public void testCreateMyKey() { + // 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()); + } + +} diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java new file mode 100644 index 000000000..7f2a7953b --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java @@ -0,0 +1,74 @@ +/* + * 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.matcher; + +import android.content.Context; +import android.text.method.TransformationMethod; +import android.view.View; +import android.widget.EditText; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +public class EditTextMatchers { + + public static TypeSafeMatcher withError(final int errorResId) { + return new TypeSafeMatcher() { + + @Override + public boolean matchesSafely(View view) { + Context context = view.getContext(); + + if (view instanceof EditText) { + CharSequence error = ((EditText) view).getError(); + return error != null && error.equals(context.getString(errorResId)); + } + + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("EditText with error"); + } + + }; + } + + public static TypeSafeMatcher withTransformationMethod(final Class transformationClass) { + return new TypeSafeMatcher() { + + @Override + public boolean matchesSafely(View view) { + if (view instanceof EditText) { + TransformationMethod transformation = ((EditText) view).getTransformationMethod(); + return transformation != null && transformationClass.isInstance(transformation); + } + + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("EditText with transformation method"); + } + + }; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 85e2f8e9d..ff0bf65be 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -125,9 +125,6 @@ public class CreateKeyEmailFragment extends Fragment { // initial values if (mAdditionalEmailModels == null) { mAdditionalEmailModels = new ArrayList<>(); - if (mCreateKeyActivity.mAdditionalEmails != null) { - mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); - } } if (mEmailAdapter == null) { @@ -137,6 +134,10 @@ public class CreateKeyEmailFragment extends Fragment { addEmail(); } }); + + if (mCreateKeyActivity.mAdditionalEmails != null) { + mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); + } } mEmailsRecyclerView.setAdapter(mEmailAdapter); @@ -157,7 +158,7 @@ public class CreateKeyEmailFragment extends Fragment { email.equals(mEmailEdit.getText().toString())) { Notify.create(getActivity(), getString(R.string.create_key_email_already_exists_text), - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this); return; } //check for duplicated emails inside the adapter @@ -165,7 +166,7 @@ public class CreateKeyEmailFragment extends Fragment { if (email.equals(model.email)) { Notify.create(getActivity(), getString(R.string.create_key_email_already_exists_text), - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this); return; } } From d0d4149e8695b833eb12e959ebe0ea1a861dd375 Mon Sep 17 00:00:00 2001 From: vectorijk Date: Sat, 25 Apr 2015 15:33:30 -0700 Subject: [PATCH 04/71] update lib MaterialDrawer to latest(2.8.2) --- OpenKeychain/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 09f3b589e..9530c3279 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -22,7 +22,7 @@ dependencies { 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.7.9@aar' + compile 'com.mikepenz.materialdrawer:library:2.8.2@aar' compile 'com.mikepenz.iconics:library:0.9.1@aar' compile 'com.mikepenz.iconics:octicons-typeface:2.2.0@aar' compile 'com.mikepenz.iconics:meteocons-typeface:1.1.1@aar' @@ -59,7 +59,7 @@ dependencyVerification { 'com.splitwise:tokenautocomplete:20bee71cc59b3828eb000b684d46ddf738efd56b8fee453a509cd16fda42c8cb', 'se.emilsjolander:stickylistheaders:8c05981ec5725be33f7cee5e68c13f3db49cd5c75f1aaeb04024920b1ef96ad4', 'org.sufficientlysecure:html-textview:ca24b1522be88378634093815ce9ff1b4920c72e7513a045a7846e14069ef988', - 'com.mikepenz.materialdrawer:library:3ef80c6e1ca1b29cfcbb27fa7927c02b2246e068c17fe52283703c4897449923', + 'com.mikepenz.materialdrawer:library:970317ed1a3cb96317f7b8d62ff592b3103eb46dfd68d9b244e7143623dc6d7a', 'com.mikepenz.iconics:library:4698a36ee4c2af765d0a85779c61474d755b90d66a59020105b6760a8a909e9e', 'com.mikepenz.iconics:octicons-typeface:67ed7d456a9ce5f5307b85f955797bfb3dd674e2f6defb31c6b8bbe2ede290be', 'com.mikepenz.iconics:meteocons-typeface:39a8a9e70cd8287cdb119af57a672a41dd09240dba6697f5a0dbda1ccc33298b', From 4c74dbe11e39e5d227b661fd1ee9a08a74aebfc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 26 Apr 2015 01:28:29 +0200 Subject: [PATCH 05/71] UI fixes for Android < 5 --- OpenKeychain/build.gradle | 10 +++++----- .../keychain/ui/CreateKeyEmailFragment.java | 7 +++---- .../ui/CreateKeyPassphraseFragment.java | 8 ++------ .../keychain/ui/MainActivity.java | 2 +- .../src/main/res/layout/main_activity.xml | 17 ++++++++++++----- OpenKeychain/src/main/res/values/themes.xml | 7 +------ 6 files changed, 24 insertions(+), 27 deletions(-) diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 09f3b589e..2d4e8a4d3 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -6,8 +6,8 @@ dependencies { // NOTE: libraries are pinned to a specific build, see below // from local Android SDK - compile 'com.android.support:support-v4:22.1.0' - compile 'com.android.support:appcompat-v7:22.1.0' + compile 'com.android.support:support-v4:22.1.1' + compile 'com.android.support:appcompat-v7:22.1.1' compile 'com.android.support:recyclerview-v7:22.1.0' compile 'com.android.support:cardview-v7:22.1.0' @@ -45,8 +45,8 @@ dependencies { // Comment out the libs referenced as git submodules! dependencyVerification { verify = [ - 'com.android.support:support-v4:74cb322740317b11a785eee1a94969426fade946123c4ae3f471276adaaaf54b', - 'com.android.support:appcompat-v7:6cc7fc2df4be0676f78ecfc5d3cda388e59890d11308811944f54efd84b047b7', + 'com.android.support:support-v4:1e2e4d35ac7fd30db5ce3bc177b92e4d5af86acef2ef93e9221599d733346f56', + 'com.android.support:appcompat-v7:9a2355537c2f01cf0b95523605c18606b8d824017e6e94a05c77b0cfc8f21c96', 'com.android.support:recyclerview-v7:522d323079a29bcd76173bd9bc7535223b4af3e5eefef9d9287df1f9e54d0c10', 'com.android.support:cardview-v7:8dc99af71fec000baa4470c3907755264f15f816920861bc015b2babdbb49807', 'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e', @@ -74,7 +74,7 @@ dependencyVerification { // 'OpenKeychain.extern.KeybaseLib:Lib:c91cda4a75692d8664644cd17d8ac962ce5bc0e266ea26673a639805f1eccbdf', // 'OpenKeychain.extern:safeslinger-exchange:d222721bb35408daaab9f46449364b2657112705ee571d7532f81cbeb9c4a73f', // 'OpenKeychain.extern.snackbar:lib:52357426e5275412e2063bdf6f0e6b957a3ea74da45e0aef35d22d9afc542e23', - 'com.android.support:support-annotations:9c59286413a2bb93e199c73261e58d5af32da7ae0a12cbd075f581a5de1fb446', + 'com.android.support:support-annotations:7bc07519aa613b186001160403bcfd68260fa82c61cc7e83adeedc9b862b94ae', ] } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 85e2f8e9d..3d7b31905 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -125,11 +125,7 @@ public class CreateKeyEmailFragment extends Fragment { // initial values if (mAdditionalEmailModels == null) { mAdditionalEmailModels = new ArrayList<>(); - if (mCreateKeyActivity.mAdditionalEmails != null) { - mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); - } } - if (mEmailAdapter == null) { mEmailAdapter = new EmailAdapter(mAdditionalEmailModels, new View.OnClickListener() { @Override @@ -138,6 +134,9 @@ public class CreateKeyEmailFragment extends Fragment { } }); } + if (mAdditionalEmailModels.isEmpty() && mCreateKeyActivity.mAdditionalEmails != null) { + mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); + } mEmailsRecyclerView.setAdapter(mEmailAdapter); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java index 32173edf7..3379e0a6d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java @@ -21,7 +21,6 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.text.Editable; import android.text.method.HideReturnsTransformationMethod; import android.text.method.PasswordTransformationMethod; import android.view.LayoutInflater; @@ -37,9 +36,6 @@ import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.widget.PassphraseEditText; import org.sufficientlysecure.keychain.util.Passphrase; -import java.util.ArrayList; -import java.util.Arrays; - public class CreateKeyPassphraseFragment extends Fragment { // view @@ -111,8 +107,8 @@ public class CreateKeyPassphraseFragment extends Fragment { // initial values // TODO: using String here is unsafe... if (mCreateKeyActivity.mPassphrase != null) { - mPassphraseEdit.setText(Arrays.toString(mCreateKeyActivity.mPassphrase.getCharArray())); - mPassphraseEditAgain.setText(Arrays.toString(mCreateKeyActivity.mPassphrase.getCharArray())); + mPassphraseEdit.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray())); + mPassphraseEditAgain.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray())); } mPassphraseEdit.requestFocus(); 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 05cf64092..f571ba1e6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -64,7 +64,7 @@ public class MainActivity extends AppCompatActivity implements FabContainer { transaction.replace(R.id.main_fragment_container, mainFragment); transaction.commit(); - mToolbar = (Toolbar) findViewById(R.id.activity_main_toolbar); + mToolbar = (Toolbar) findViewById(R.id.toolbar); mToolbar.setTitle(R.string.app_name); setSupportActionBar(mToolbar); diff --git a/OpenKeychain/src/main/res/layout/main_activity.xml b/OpenKeychain/src/main/res/layout/main_activity.xml index 4a07053ea..45df0df71 100644 --- a/OpenKeychain/src/main/res/layout/main_activity.xml +++ b/OpenKeychain/src/main/res/layout/main_activity.xml @@ -1,19 +1,26 @@ + android:background="?attr/colorPrimary" + android:minHeight="?attr/actionBarSize" + app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" + app:popupTheme="@style/ThemeOverlay.AppCompat.Light" + tools:ignore="UnusedAttribute" /> + android:layout_width="match_parent" /> \ No newline at end of file diff --git a/OpenKeychain/src/main/res/values/themes.xml b/OpenKeychain/src/main/res/values/themes.xml index 96d09151d..6ac09c5d7 100644 --- a/OpenKeychain/src/main/res/values/themes.xml +++ b/OpenKeychain/src/main/res/values/themes.xml @@ -8,17 +8,12 @@ @color/primary_dark @color/accent + true - false true @style/MySearchViewStyle - - - @style/ThemeOverlay.AppCompat.Dark - - @style/ThemeOverlay.AppCompat.Light From 08d25f3685cffb7402bb6dd824bc0666bfe9a1c2 Mon Sep 17 00:00:00 2001 From: Daniel Ramos Date: Sun, 26 Apr 2015 18:56:37 +0100 Subject: [PATCH 06/71] -removed unneeded code from previous commits. I tested on a KitKat(4.4.2) and Lollipop (5.1) phone and everything seems to be preserved as expected. --- .../sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index ed4cf5b8a..473383e22 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -135,9 +135,6 @@ public class CreateKeyEmailFragment extends Fragment { mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); } } - if (mAdditionalEmailModels.isEmpty() && mCreateKeyActivity.mAdditionalEmails != null) { - mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); - } mEmailsRecyclerView.setAdapter(mEmailAdapter); From c0fbafde688f2f60f475121bf8c8b8ef2a59ce6d Mon Sep 17 00:00:00 2001 From: Daniel Ramos Date: Sun, 26 Apr 2015 19:11:51 +0100 Subject: [PATCH 07/71] -Removed unneeded view references from the fragment class since they are not used outside of createView. -cleanup --- .../keychain/ui/CreateKeyEmailFragment.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 473383e22..035945af3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -49,9 +49,6 @@ import java.util.regex.Pattern; public class CreateKeyEmailFragment extends Fragment { private CreateKeyActivity mCreateKeyActivity; private EmailEditText mEmailEdit; - private RecyclerView mEmailsRecyclerView; - private View mBackButton; - private View mNextButton; private ArrayList mAdditionalEmailModels; private EmailAdapter mEmailAdapter; @@ -92,9 +89,9 @@ public class CreateKeyEmailFragment extends Fragment { View view = inflater.inflate(R.layout.create_key_email_fragment, container, false); mEmailEdit = (EmailEditText) view.findViewById(R.id.create_key_email); - mBackButton = view.findViewById(R.id.create_key_back_button); - mNextButton = view.findViewById(R.id.create_key_next_button); - mEmailsRecyclerView = (RecyclerView) view.findViewById(R.id.create_key_emails); + View mBackButton = view.findViewById(R.id.create_key_back_button); + View mNextButton = view.findViewById(R.id.create_key_next_button); + RecyclerView mEmailsRecyclerView = (RecyclerView) view.findViewById(R.id.create_key_emails); // initial values mEmailEdit.setText(mCreateKeyActivity.mEmail); @@ -222,7 +219,6 @@ public class CreateKeyEmailFragment extends Fragment { Messenger messenger = new Messenger(returnHandler); AddEmailDialogFragment addEmailDialog = AddEmailDialogFragment.newInstance(messenger); - addEmailDialog.setTargetFragment(this, -1); addEmailDialog.show(getActivity().getSupportFragmentManager(), "addEmailDialog"); } From 8dbb82a8b61922e788e844e747b43f1838e6e58f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 27 Apr 2015 13:51:39 +0200 Subject: [PATCH 08/71] profiling says: caching qrCode bitmaps is a good idea --- .../keychain/KeychainApplication.java | 14 ++++++ .../keychain/ui/util/QrCodeUtils.java | 44 ++++++++++++------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index d26ccbe57..710dbf8aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -24,6 +24,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Build; @@ -40,6 +41,8 @@ import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.TlsHelper; import java.security.Security; +import java.util.HashMap; + public class KeychainApplication extends Application { @@ -100,6 +103,17 @@ public class KeychainApplication extends Application { checkConsolidateRecovery(); } + public static HashMap qrCodeCache = new HashMap<>(); + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + + if (level >= TRIM_MEMORY_UI_HIDDEN) { + qrCodeCache.clear(); + } + } + /** * Restart consolidate process if it has been interruped before */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java index b8d4ea7d2..5f71abdab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java @@ -29,6 +29,7 @@ import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.util.Log; import java.util.Hashtable; @@ -40,36 +41,45 @@ public class QrCodeUtils { /** * Generate Bitmap with QR Code based on input. - * - * @param input - * @param size * @return QR Code as Bitmap */ public static Bitmap getQRCodeBitmap(final String input, final int size) { + try { - final Hashtable hints = new Hashtable<>(); - hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); - final BitMatrix result = new QRCodeWriter().encode(input, BarcodeFormat.QR_CODE, size, - size, hints); - final int width = result.getWidth(); - final int height = result.getHeight(); - final int[] pixels = new int[width * height]; + // the qrCodeCache is handled in KeychainApplication so we can + // properly react to onTrimMemory calls + Bitmap bitmap = KeychainApplication.qrCodeCache.get(input); + if (bitmap == null) { - for (int y = 0; y < height; y++) { - final int offset = y * width; - for (int x = 0; x < width; x++) { - pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT; + Hashtable hints = new Hashtable<>(); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); + BitMatrix result = new QRCodeWriter().encode(input, BarcodeFormat.QR_CODE, size, + size, hints); + + int width = result.getWidth(); + int height = result.getHeight(); + int[] pixels = new int[width * height]; + + for (int y = 0; y < height; y++) { + final int offset = y * width; + for (int x = 0; x < width; x++) { + pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT; + } } + + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + + KeychainApplication.qrCodeCache.put(input, bitmap); } - final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - bitmap.setPixels(pixels, 0, width, 0, 0, width, height); return bitmap; - } catch (final WriterException e) { + } catch (WriterException e) { Log.e(Constants.TAG, "QrCodeUtils", e); return null; } + } } From 40834d1fcd2208bdaf3a2faff1184e168f3c2e3f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 27 Apr 2015 14:40:52 +0200 Subject: [PATCH 09/71] add distinct status for messages signed by "your" key --- .../keychain/ui/DecryptFragment.java | 13 +++++++++++++ OpenKeychain/src/main/res/values/strings.xml | 1 + 2 files changed, 14 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 651b56ab0..9c51893ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -231,6 +231,7 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements KeychainContract.KeyRings.IS_REVOKED, KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.VERIFIED, + KeychainContract.KeyRings.HAS_ANY_SECRET, }; @SuppressWarnings("unused") @@ -239,6 +240,7 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements static final int INDEX_IS_REVOKED = 3; static final int INDEX_IS_EXPIRED = 4; static final int INDEX_VERIFIED = 5; + static final int INDEX_HAS_ANY_SECRET = 6; @Override public Loader onCreateLoader(int id, Bundle args) { @@ -283,6 +285,7 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements boolean isRevoked = data.getInt(INDEX_IS_REVOKED) != 0; boolean isExpired = data.getInt(INDEX_IS_EXPIRED) != 0; boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; + boolean isYours = data.getInt(INDEX_HAS_ANY_SECRET) != 0; if (isRevoked) { mSignatureText.setText(R.string.decrypt_result_signature_revoked_key); @@ -302,6 +305,16 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements onVerifyLoaded(true); + } else if (isYours) { + + mSignatureText.setText(R.string.decrypt_result_signature_secret); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED); + + setSignatureLayoutVisibility(View.VISIBLE); + setShowAction(signatureKeyId); + + onVerifyLoaded(true); + } else if (isVerified) { mSignatureText.setText(R.string.decrypt_result_signature_certified); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED); diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 4d92dac20..ffdd86259 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -287,6 +287,7 @@ "Not Signed" "Invalid signature!" "Signed by unconfirmed key" + "Signed by your key" "Signed by confirmed key" "Signed by expired key!" "Signed by revoked key!" From 4ba2e4bcdd9c3b5f472b1502e55bcf753ee44b58 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 27 Apr 2015 17:45:50 +0200 Subject: [PATCH 10/71] display creation dates for ambiguous user ids --- .../keychain/ui/adapter/KeyAdapter.java | 21 ++++++++++++++++++- .../ui/adapter/SelectKeyCursorAdapter.java | 20 +++++++----------- .../src/main/res/layout/key_list_item.xml | 16 ++++++++++++-- OpenKeychain/src/main/res/values/strings.xml | 2 +- 4 files changed, 43 insertions(+), 16 deletions(-) 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 6f19fc6ed..f09dc1a4f 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 @@ -27,6 +27,7 @@ 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; import android.view.ViewGroup; @@ -88,6 +89,7 @@ public class KeyAdapter extends CursorAdapter { public Long mMasterKeyId; public TextView mMainUserId; public TextView mMainUserIdRest; + public TextView mCreationDate; public ImageView mStatus; public View mSlinger; public ImageButton mSlingerButton; @@ -98,6 +100,7 @@ public class KeyAdapter extends CursorAdapter { mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon); mSlinger = view.findViewById(R.id.key_list_item_slinger_view); mSlingerButton = (ImageButton) view.findViewById(R.id.key_list_item_slinger_button); + mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation); } public void setData(Context context, Cursor cursor, Highlighter highlighter) { @@ -125,7 +128,7 @@ public class KeyAdapter extends CursorAdapter { 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) == 1; + boolean hasDuplicate = cursor.getInt(INDEX_HAS_DUPLICATE_USER_ID) != 0; mMasterKeyId = masterKeyId; @@ -165,6 +168,22 @@ public class KeyAdapter extends CursorAdapter { mMainUserId.setTextColor(context.getResources().getColor(R.color.black)); mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black)); } + + if (hasDuplicate) { + String dateTime = DateUtils.formatDateTime(context, + cursor.getLong(INDEX_CREATION) * 1000, + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + + mCreationDate.setText(context.getString(R.string.label_creation, + dateTime)); + mCreationDate.setVisibility(View.VISIBLE); + } else { + mCreationDate.setVisibility(View.GONE); + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index 3308a4500..1ccb910d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -20,7 +20,7 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; import android.database.Cursor; import android.support.v4.widget.CursorAdapter; -import android.text.format.DateFormat; +import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -36,10 +36,6 @@ import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; - /** * Yes this class is abstract! @@ -138,14 +134,14 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { boolean duplicate = cursor.getLong(mIndexDuplicateUserId) > 0; if (duplicate) { - Date creationDate = new Date(cursor.getLong(mIndexCreation) * 1000); - Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - creationCal.setTime(creationDate); - // convert from UTC to time zone of device - creationCal.setTimeZone(TimeZone.getDefault()); + String dateTime = DateUtils.formatDateTime(context, + cursor.getLong(mIndexCreation) * 1000, + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); - h.creation.setText(context.getString(R.string.label_creation) + ": " - + DateFormat.getDateFormat(context).format(creationCal.getTime())); + h.creation.setText(context.getString(R.string.label_creation, dateTime)); h.creation.setVisibility(View.VISIBLE); } else { h.creation.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/res/layout/key_list_item.xml b/OpenKeychain/src/main/res/layout/key_list_item.xml index fbe5f1326..6078b898f 100644 --- a/OpenKeychain/src/main/res/layout/key_list_item.xml +++ b/OpenKeychain/src/main/res/layout/key_list_item.xml @@ -26,7 +26,7 @@ android:id="@+id/key_list_item_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/label_main_user_id" + tools:text="@string/label_main_user_id" android:textAppearance="?android:attr/textAppearanceMedium" /> + + + "File compression" "Keyservers" "Key ID" - "Creation" + "Key created %s" "Expiry" "Usage" "Key Size" From 91b774d22334b91c1867b867c515d1fcd31e34da Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 27 Apr 2015 19:48:58 +0200 Subject: [PATCH 11/71] prevent crashes in EncryptKeyCompletionView --- .../keychain/ui/widget/EncryptKeyCompletionView.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 3d2e8b9df..4e691d962 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 @@ -110,7 +110,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView if (getContext() instanceof FragmentActivity) { mLoaderManager = ((FragmentActivity) getContext()).getSupportLoaderManager(); - mLoaderManager.initLoader(hashCode(), null, this); + mLoaderManager.initLoader(0, null, this); } else { Log.e(Constants.TAG, "EncryptKeyCompletionView must be attached to a FragmentActivity, this is " + getContext().getClass()); } @@ -154,6 +154,14 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView mAdapter.swapCursor(null); } + @Override + public void showDropDown() { + if (mAdapter.getCursor().isClosed()) { + return; + } + super.showDropDown(); + } + @Override public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { super.onFocusChanged(hasFocus, direction, previous); @@ -171,7 +179,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView } Bundle args = new Bundle(); args.putString(ARG_QUERY, text.subSequence(start, end).toString()); - mLoaderManager.restartLoader(hashCode(), args, this); + mLoaderManager.restartLoader(0, args, this); } } From 2b6bb57c1c1c6202ab5d00d6351424e1ac0fb1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 28 Apr 2015 11:06:54 +0200 Subject: [PATCH 12/71] Display development build status --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 48b649b22..b9621f19f 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ OpenKeychain is an OpenPGP implementation for Android. For a more detailed description and installation instructions go to http://www.openkeychain.org . -### Travis CI Build Status +### Travis CI Build Status of development branch -[![Build Status](https://travis-ci.org/open-keychain/open-keychain.png?branch=master)](https://travis-ci.org/open-keychain/open-keychain) +[![Build Status](https://travis-ci.org/open-keychain/open-keychain.png?branch=development)](https://travis-ci.org/open-keychain/open-keychain) ## How to help the project? From 871764219897d0105797fd59fe47fba41aba012b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 28 Apr 2015 18:18:14 +0200 Subject: [PATCH 13/71] fix "select all" in key list multi-select --- .../org/sufficientlysecure/keychain/ui/KeyListFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 96ce101b5..1355bd3e6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -240,7 +240,7 @@ public class KeyListFragment extends LoaderFragment } case R.id.menu_key_list_multi_select_all: { // select all - for (int i = 0; i < mStickyList.getCount(); i++) { + for (int i = 0; i < mAdapter.getCount(); i++) { mStickyList.setItemChecked(i, true); } break; From cd55871950571035088823846739e04cac8ca432 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 28 Apr 2015 18:18:42 +0200 Subject: [PATCH 14/71] display unknown keys red rather than orange For an unknown key, there is no indication of the state the key is in. To indicate both immediate action required, and to make this status equal to its worst case (rather than *better* than its worst case), the status is displayed in red. At some point, we will probably want to download unknown keys automatically, at which point an unknown key will actually be an error state. This is an intermediate solution until then. --- .../sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index ae66b59d4..91a7d361a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -475,7 +475,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_orange_light; + color = R.color.android_red_light; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); From 8ab49b076603ade77785a9bfb4a0f7a13bad483a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 28 Apr 2015 18:21:51 +0200 Subject: [PATCH 15/71] fix crash when moving focus from EncryptKeyCompletionView with unknown text --- .../keychain/ui/widget/EncryptKeyCompletionView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4e691d962..525bc26ca 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 @@ -101,7 +101,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView /*if (completionText.startsWith("0x")) { }*/ - return null; + return ""; } @Override From c9e0acec50268b2338e860ec49d4613adfeeea1e Mon Sep 17 00:00:00 2001 From: William Faulk Date: Tue, 28 Apr 2015 19:10:45 -0400 Subject: [PATCH 16/71] Update README.md with new build info --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b9621f19f..7c4dce0af 100644 --- a/README.md +++ b/README.md @@ -30,19 +30,20 @@ Development mailinglist at http://groups.google.com/d/forum/openpgp-keychain-dev ### Build with Gradle -1. Get all external submodules with ``git submodule update --init --recursive`` -2. Have Android SDK "tools", "platform-tools", and "build-tools" directories in your PATH (http://developer.android.com/sdk/index.html) -3. Open the Android SDK Manager (shell command: ``android``). -Expand the Tools directory and select "Android SDK Build-tools (Version 21.1.1)". +1. Clone the project from GitHub +2. Get all external submodules with ``git submodule update --init --recursive`` +3. Have Android SDK "tools", "platform-tools", and "build-tools" directories in your PATH (http://developer.android.com/sdk/index.html) +4. Open the Android SDK Manager (shell command: ``android``). +Expand the Tools directory and select "Android SDK Build-tools (Version 21.1.2)". Expand the Extras directory and install "Android Support Repository" -Select everything for the newest SDK Platform (API-Level 21) -4. Export ANDROID_HOME pointing to your Android SDK -5. Execute ``./gradlew build`` -6. You can install the app with ``adb install -r OpenKeychain/build/outputs/apk/OpenKeychain-debug-unaligned.apk`` +Select everything for the newest SDK Platform, API 22, and also API 21 +5. Export ANDROID_HOME pointing to your Android SDK +6. Execute ``./gradlew build`` +7. You can install the app with ``adb install -r OpenKeychain/build/outputs/apk/OpenKeychain-debug-unaligned.apk`` ### Run Tests 1. Use OpenJDK instead of Oracle JDK -3. Execute ``./gradlew test`` +2. Execute ``./gradlew test`` ### Build API Demo with Gradle @@ -55,7 +56,8 @@ Select everything for the newest SDK Platform (API-Level 21) We are using the newest [Android Studio](http://developer.android.com/sdk/installing/studio.html) for development. Development with Eclipse is currently not possible because we are using the new [project structure](http://developer.android.com/sdk/installing/studio-tips.html). 1. Clone the project from Github -2. From Android Studio: File -> Import Project -> Select the cloned top folder +2. Get all external submodules with ``git submodule update --init --recursive`` +3. From Android Studio: File -> Import Project -> Select the cloned top folder ## Libraries From 41968206d3deed789dd5b35468a8d8487755234c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Wed, 29 Apr 2015 20:43:46 +0200 Subject: [PATCH 17/71] Use own gradle wrapper to verify downloaded gradle package via SHA-256 sum. See https://github.com/gradle/gradle/pull/448 --- gradle/wrapper/gradle-wrapper.jar | Bin 52141 -> 53322 bytes gradle/wrapper/gradle-wrapper.properties | 1 + 2 files changed, 1 insertion(+) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 085a1cdc27db1185342f15a00441734e74fe3735..497f80dd6b5dccb96a97e72c70967bbb10242a11 100644 GIT binary patch delta 22036 zcmZ7d1xzJfur&bfPu4oWBCU5e@kD|a}vtPApgfq(L#d#+vqpz|Cl5?@Pq%c zOXMr;THsye@Sge_*k29+FBTS-3A4@O9A?Vk zR-Mceb9mMO(bUqM#8NIXy)uonsp%;*V|GlyuB#KcdgMEd8#p#v+F>BXq73B^i3WOv z_WVGB$S%_SL&eYAEEzx|N`n8j-%GB~jm}?}n;hY%L!lQ)Z5CZlSCAuL>%O0}XCI+( zKG5kkKazlRI~BK2@O2@|D&;WoYc^0fa9cphVAGxX+Eu^_mQWDqM5G z@mjszYcO)k=}YV}exQi#ae#<_GpfR8HdSG)I+7g9a!3rlM1QlQ9QUqY{}+j?yxnMs z%~xg!^0hIt#`zwb?np$&OPT()G8FWh3FJJzMCdIjfDF?Rr0m;E9%?(ftB;gY7py$o zqVFv_?4s{2J7k02OS1sxABN!*k6|MC7au^J_k&&=2>lE9%kcj=Y7fobxlFtxcKE8I z^d%1sA^4Z<(?R#){Q5;KO*yS@v@KmW)BMBq&Ull3tgS2&6EGQ>VKs|Fcr~GI3Iq&B zh9EO!V3cnPciS!9jT+xA_Ra`|GGm~D2Tb;wk=x8aPe3YYMs*kT#v=x&s5l!lWegWc=kXfTT!EUC?BQR}Y* zigc_sDc&FGzUOVZc{?uHtw*3)rw%LQ0fB4qEs3cAyjdv-GDtH8cdLNdfb<+5X<%m2EsBW zl{Cf3GzX!>%V%-fg>qD+yVbie1X+vEd}$}+CX`81`9dowOQGlj0evQT&C!L<&K5&D z#Z_yw8PTGw88RU=;Cgz;NSgwbeX&Atm+KJ*{Du+nu{+MVBI%^KP1O;x@H#N;^9cS` zAdlCc{!nOH%ay;kYO2R1SRX25d3VjRaEA!F3-=a+RUkgWItns6;KIGuM+`I{=6p5} zpcq&FsgrkiL{Aksh3Nd+6$m_grTHoWJ+~(g#LqTw)vMe&5d9qhx^;(RBKXvz70RQd z81RP3eY3Zk_8*9eOo#HI=P=1YmniR~{g&BZsN-v&Rih5RCr^w2o8UaOFo>vGk{FXH zvV1;-nI2oEB+B|=K_X_}^&lmq9mhd)nB0I7r=_!e=n(=ar#NfFNSx5pO5fg5ma}dn zZ81vlJELOVvR8#G8Xrlj*n~5~IzBUX+@ylpmmQ4zLE!iC<&U|#hu7L!rZGe;s*D@y zb+>#c?l9Z+lzLycP8YY%0x!4Dt*9BtmOi;?qhoyy*NYG}(Sg zfKjZ%7$kLEQxC}}$-yEJmA=ky zE855b(^bSvzu-F#D^$aiBfKW0@X<-r;4AgDeA^wSrJ>D@F%LT0jN)dP@_sf0oGM(G zvOl}u!g)eW;Gv4aQF>)$P{)$2TX9!Iv4KjlHoqDxnfG+N zPVeX7^nV$(BsNJlT*nebUv^Nr-gxo-q;q%!Ld*r|OTn+t{i)aS#7(`srrN{2nHC?8 z4{(xtV9%LhdT`QGL!&A%r|BA$wP)_;w9bO0Qg9!e5nsk{$`bwYJG)mEe5y}o(-0tR z>z+MHVzKj~;fk)W9eV8veLD1^p%qHH_U&7ZM?ZrRN4C##(CF7qYafEw?|ltIqnQ*5 zpdn44oE{CC?i#U#j?I<|ukKddpiJVWqTVJIVcNjVJ(lxY5g~GUAy?FVI2A8)nsXT! z4XlsJdLL)n=ARIuB%I_84y&Lb7vY)*93)4k4ji8yHRaeMYkX^I`9fa=T@fGbj$d3K zE8F5VOs$ea9?W$rckt$kdXw9tdX#;ez|K|$GZmHiq7CJ@rP^p@jF zT(RiwG^ZQNV`n`v!8#Kbt3@LvJsfYGl&kye+NV5SHbVl%42nHSN@q2LffEZ7BrUV36w$ju1A*F3sQ>xh^`s}%A z+kU+EU(1LKL55F7aoEdq2piJDD6HSI5=6kXFx4DPcMQU6mK%uL8dmLrG^YmDoidtR zY>UL?*D8y|6xZ;Jxn{(96o*FpL2aQMNpPOXEtl=YGV*9aHOYR^9qJdoP;|vQI%3H` z@ACk92?N6FFSf#00#YNO zQj*m1nz3kh&@P8guwD<)!in&~-RVi7$6_jO$|v@tz2|f??(o@X`F)l$f<1>ycBNi?Z+RKGtlPDK}Y6>tK(%py=sfxrqsr`Tgor3zutc zPV9s+@+s0(*UitR&Pe-4)TS1FyS8Nf&`;#nOSz0<9%t$;HI`lAa7I*PrxUV;y{sT+ z7_XXV=yxXP5lV=oRob`smk&DIb^aJcQ5zbkgZQE;9T0fbu*RjCd9`I)3h2^#mEVE? z-ZpFQxSUGTCpOzNGX3hgt878)$M1J+sWe?V$~_>%>dJoboIzEt(=itJpDFVUWL^i6 z5T$#x)-3loESXJU_a(jtA>VyCGCW$BZXqm2y7B3Ri$7~bPBuSF}~?X z7=S0Hi{SLfjd`f)^aVCj!t}caQYg{^Ez$PWl0(7c!*}wzLA?kjyj8I#gEO=QB2(=h z!DsjGAWd~lx7%WIShuo!=3GZr$!*E`#nfDY^}9TLLIr%scnjQ zFcsg3Q*_&m37Y?c{~yo(A1W=wf&`8HAA{yDXyZl&16xY^BB2Fp!+H{qx%s1N{jS&M zj;9^-rUXYPx`H-FhxcucM9F3%T%If6hW znR*Zmg7qhEzGOT}oc^V8_E(_*$Wrt-|F8O4pXk#b!e^W;@>3tZ#?s%3kH2AO zu#0=@>FSa5$SL$0C3Fd+VO+n(uu+0}X*nVX$wK7Bszcj>^3ulwPg*a&r+ z6-jHf-I^u#hM0$$XltRb#3^e|26SsKkVuE4Xgo4|jElO(RvMl?vM-M?R@|xg)Vqu` zI3aD7uE~DDMO)0lYlK_nc7+m5QJ?HB^C3kxue8Z7uQ=Zul{dqi__-eT%_2t{Og1Z_ zh+q?s(P#BZX||BA&KEbrb0Oc(DvSNQg4`Y{lo!IY?|${5JO9#$-KIZc-ZuJ5(ak8l ztr@sh7MW{SRI0JLjU> zu$pG64pWIub#vSU_gUj3Ju7xk5=_o6>P#-N@dew(SCsKGhBRj z)e2#ug09XM9MHVq6teNRZ7XVd3!bHl&bIo?8m5k(&1b2Mj~vZuW8HZh>gcbh^?VbO z)dpH1dVT2AtI7`du>i-phopkIy_d=aMwTbxW|dfLfq-OMhI6_*R?B|+8b$uliS*_w z(UNTQnvV!ijplts@K0J`PQ_QO&OjmV@Yad3Zg1SWQ|TM=d+tyIE^t%t!u<-lJ4Wi}VE>8r=GHoXCao`{sbVZY8+<>0ody#oPMdk^IHhovW5 zlLz0^apG_lg`31up=s*s7|b|$d`@0l>GxilDY(qCWag-W)6M8dA-gEmV|qR!Q9>yp z>FLq{C-e-7t|sM8dzpcD3wB;9fR)~Ntwi8@%WmD4JMvGjpRK>Y+7tRAURYDoag%{4 z=@jrgGFzki7SjMD@s5k+1J^hLF)OJnKFf7e06mqk)Ut((06lqwa!%G>7BMlmErt8| zys6=z((LpUNqzbU@!O^=wjC#Z2q57(jPQg=a%{ITQ8N=Al+O}i8#F8v?8_Wbjm!62 zx#08%3e9zgW4nMi2AWk_QY`uujS5g``lfBP-y-*A!73)^9DZ3ES%9$%HBdQjU!TKm zT0TE`(Avo0(+StI>ptJRwrdAnz8~JWd|1=CL1} z363<)Y!<7(hKkuKEQK*8p%3{t)U~zs%}A_^RO|^lD2fbs(!u#Ls8Jni*azT=L^H!8 z2}iLb-QZesP$hkwiwC(7;3zWMSvadq!==O`ztebJR`H4Q;izqTJ(*|EP@e(SAtKP2 z1|iu!0f*341a1@4hwa<9zm=Vr@0rh+YaGuwHA5SPerv}?X1+a~1jQS?6xKoE^o9>j zg^@V-+cG)k%__Lp-9D0UG+w~ewbHG}PCJpwhfT)r%-hhl`-~YqGnaP*SaOnU9ZkJk zUDORh=@=n8ZO!R5|!*xPVY$J!i1q0krRU* zbF(G?7+YDZG`ng-@Ugh#fuk!2`T`~< zX;Tc@SR(#B#yPRl;ZV2)Ap?8#Ph7UqXEGXL^o{tUEX#ocK8nBn`#SPAA7D}v_aOYd zeGj&r?h*{V4Z84pa#Ns#5@d|h1D*OR-iP~ot1ubq>MN(842YxJMv%6MEY|AD<&ev5 zUf!&mxe|hKrHwxcLH;QcQuc2CEIq_!sLgH)Cnl^zsXf|KaI$pJg3DhSsV=jJWsHs8 zn75wV=R-1)W{if6PlB1clo~)h}cHVb7;bA4M|YsRzWWNZHLCq8PZTZYN6a$t4ta_6f{CGR0bE-&oUA6!iE(973}C+VdeC zLvkS1;5#cY$_4cQ4*UTcdv%KWP778}>+LxpqKah*LZ$N+0ntElDm{cX+2^?|T7M*q zpHU1tyZQSN z0~$FJfAaS7?0#Yk5!myTdIbz$OW6~;2?;IMbq%e--2lOw?61cQJO6CVC0yu;d$A-^ z>0As}sUEqfvgPz#ZeLI^92pr4>&pqx?u6KrRgk? zYGQ3%_5!EXPP~Rf2#NWGd6lGnA}et@M}^AMCMo0?-AI_9U2+x70hJ6sn!hTDqh=EG zG_YFSuCMo!=bg#0E|)Pi2cg)Z7|T)IByOJVcS$>2Z6tfKIg~ObT&PRT&P#(j8AYHa zkK7jZr%hZ+0lUhHzC=NE47u(|N<;Cc=5e)mO#i~Sb5!dOHj*vG+FK2;b>{9!t#5tn zQP)uxsqAy&8OSC=tCzjd9=q7VC*8DEUCyYj9~9o?$GFO!NSBWE)0q3aE>g_4bIuR_ zEFCOys}ztOd#4XLPzJFpHaTKC1NU4M6nUlPNYG)4Z2rctI2H3C;WRa3`^aA0pj`^A z0buo%hth#sP2m~DJX>YiAI7b-SOQa8m|yxjgSZ~-c};Q-%*R--OFj^=W5iGXtnhQe zp0kLxTzh!XbxV3CRx)gc&MT36hdUz@?gJ`;G<^ArSJ-{fvm2$a5(907I?b6#y0YTn z2urjX!>%|+R8OQSQ$i#l=Z>c?q1sFPIAE8+pOAbv+L+&DzCs8n{!d)UYFO6krHhsw zo#DO066RVmHkRG81EHl=SeBN(vX8ax7_$WivLek@4KCPB1zhtcOf|*$%`Kd47*76p-TTHl7gWPrNM zIPtaJOyOQkgxRGd3!#pzgqGI1g;YuE_xKvUymB-A>Do&4T%s$~x{@=>?JF8CeKd?^ zC(Pa~J*WuKmI6W$YstoI)ZX8>FNb!$|A6_Sg)$9&p)Nox*9^SEM+ZJpy1zF(4y^}$ z(;V$Ef&@B=1I0jCZ|_-kD%eX@mB8H63rnx{zX(h*e==bV3(eJ;cHp=x2n@5?4;73X$F2UiK%)y#L6a&uo`fEA3OOb?VP_1|52}_c|_9+=_ z4rTH@asp(J&eP$i3C>2eh?cM-6c?-`W-{fP;deH_PA>I6duswBe}w$yiv)6NL2s;}2i_vldAIi{6Ihu1_Hv{g8^92a<`E|wT z+7%50(flKYV?I}!WBr4LK`=!y#Ggok;K$CP?q*2H0a@uUO*6QGpu0-4>?;JZX=2Xlbqn$+x;*lJMfx6~}euOLt;y2mYXt=8Ox zOyF)Zbi*yN-A1#C0Z%tAM9x{uq_!b`( zIT#g**@9TX$lrA0mo9gokFS3q8sDO%i@&jbAuOKLt6Jg9Jkc+n>OJB5eUqVVP*BBl zZ=H`bwLoibdBS{3ihl({)#RU&3P4$Le`5I&tJsSf_~iQIt9+4LvWSI}_?ka)Fb_K% zkpm&IFy2OwYMj#W(^m zVHAPWEd<}j{A#3a!8^1Tc{bMP~}%Zy2H^dH~WnV`0fij01p zYWz6;cAl2+^E>%$m_K(oWAhKz2^4ut)W=hcGSKvq5?QkC&v&`UtV};_R==}{nOkrq zHnaPT4U4-epruhj!u%+zCmM8wt%Br7CendXSuXTk+MGikk-o{^y zJT9#iM?%Q{Ape;`itG{riPdp(aw{aTy9~k@gvuHGOKS%EGf*fO?9IN47wjc0!7%RS z@6FWPO0d{~(@BlsSrZ40p=IqAJK$^(wl~f6N%iGz*lefDeCGSmf7`n*ac8xVn{R^l z{rdG)kDJ%+>}28l75>GG`B(HbRiAG1tR{!y*HqkgUj8DEN@CPwZ%AVLr&`+AoaJy$ zAXRzg?w;wBUore&wEySaEx8-jhtT=2CPoIH6bnTQ^o#9582o`2Y{8X@3q-Sp*0U3= zw$p%v3lw?h;1d;XN3{6x;zlIt55sVgj7~wEVjpU2diUJD0Rx%XjHG$D~|X!(zH+6kTwcxK3EosrGM_wYD}6Bf9Q{~({uQ`Ee5bc z6`V3@3ZIt2P$Q+pY8QK@Be*XJP6Y#uLg(wqdBVh@_)L^__HHwwM3GCaaSBkX=3_)N zHhcws*Qqs|YBwAHzt80t^q_|a0S4v>1qLRblmLa3B;3sgEKxIXz!1R_f=nK{UOcQc zu1VQk?Nm39GFmXkhmVPXXH%$Dkk2b+4dB``U>A^n0~daEcpBl(e1-f_85Q`ELJE%g zVsew?bJOuMowGsmz0V(3dC(N|aXhO&o~J%@Pyy3MTgikXgz9>iy&+hF{`wm%>)ThK8yL|JU{cZ~%nkdmBLTIRVf9m7a+nlMY6xVq#`cb;d|=LTIX7rT3COD(pkx(5z5!K zo0}K8p~QwmG?n$Bs@KCc#VZ$A$?%5GSd$c~f4R3rt+cxD)xTTw=rvP5VzrjrDj2mMQ(Y!}7TtSgLj#l)}wRxUY^rH4|h}7B0qb z3B}fkf%N{FWp4?ANFlXM##OCnV%P?k+#oNn#DCohWUA+FYkdAnF46)lCD-*O>8})wooqD&jmXwadnRfLo1sKA{x! z*;QfS%Bok6xh#?)7=&z@m9=|T0d*3-ljdwdLus%=}S!jP&d#32}&OSQN-!6Fhp1sOvg zfLhe;Cu6HQAA|-6g<$HqgicafqZRcLQ*qZPn^z&(itNq5w`CA*_{h`&@=R2X4>~`wGwwY+hMjxBCX*N@AidM9{ZvK3EnLdbQeS&7n*^EFef| zl0yn;5)dX{9{np)9>Xhy5&bK|shMxhK7lU-NC0}TDd%xrVywx4gEK5fc}$s^=^Z!Y z9sIj$qW3h&n|#1W{StqyFosP(-U+BOpwwP$oL#d$Q>-wnzS_fqu|Bt zCeWt>x#Ib^E(Eq0(O}CQbR+Pl4{|~+*RO&ldxC7w`}abAmawwcD_ceOObH}MK6O_! zIy+fL1{ti2&=ArL>`5~&PrdWhU;in-tjVVjB^~=r9>!j(ZMiZz0sxNnnNLM-?(@oB za@wOF_&n}x?9)#(NpDVe3L)AjFKzk13hhzNH>DdWSOvkOnZ}>I;*P*)-pIfLCoI^{ zMc(54Jq{G5J9b&-czJYg3??##_&7kJEWj`b#}*uDIDaj#YSwl}Drl>f-HYG_yu*lG zyfc(I|C+k%sA}X)o`e=()b`8}HVfCwmVBetTrN{#i=_Vv2k0b7!PFK!vDVBEVay3q z@9yx%I?4W>Cck-$Py%ddPP0?Yr)SXah;EIbDsjS;DLL?XN#kG-w#aD`@o&ZO#dg>HlMrsR2yHmuzv79Kb@}^|Nz*ea zp>Z^+wc0s#=<9~#w6HDAq=7X>lw1j*1Bqnz=OoYms58Q!N(Sa4wxh~%^kG0O;}TSG zkuRh8sW*!?^Glcui5&~ZDZCP8-P2${;p;jP=}BO3iX(51(7T2c+hVfWexo?UqNx|l zrexSg^Vz2&uq$5pv0JO=VqcO03oOWKzm9LGYP`lkqvyBO%2x`mM}ASc)E|p$W)be6z*=BD0SE7+9B@!Coycj(>(^A6eFUBkE;&?z+ z1`~8QbpX_bCAMS$gcA|SA75Dh-zc+!#tQwPf(Q;L9s>P;>L1g{hLQ>Yh|Al*ki+yJ zap~)WP0~QY0w&ohVTz!Q{oOiU4H6FgRai)VO_rU%&jF9B!KQ(PE$u5->zGP$9J_76 zb+2Yc&UPO{(vNDM-J_RZy*13k(ZunX_j#@V{ysNn1a@*q8zz@NVay#x7>#t}^nGkb zkmsettA9omKgvqbZ2q_1_q!`io_d2YlX@phe|F?C1YmT{%ySu1$nKxWb3LSz@-kC? zyCNB`GNzXvjv&a;f?f5;{%j*{TlYBJMWZW_B}QD~?lQ+#Ll9M{XG`l$ytdm*MXtko z>~pKna%1-kpbB&M`X(dCZmH=_hZ1uj;F@C@zjlp#Gq+=}A$l&?%h%RxRPjj5;IwGx z#+%xL47lyPg|N!p-K|_R;V}2^tk&S&5Z07hqO!0=Uj|~Xt+=Yn46FS6yHZlBE73xH z1HGcn;()?`Pc>GZMDe@S?5&V^ie-CwxY*LQx6dkK)~&yK%f8MRJ~B4af}xQ;a~;=3 zm)^{ulbfm-9yjEmaSo(xf3h!zdoHZ_HRZ$7X<$q!II_ep?{l6}N-e+9q2%TKCL$~N zR~VRT_`lk6{? zNtPrc$wXUd_(Rw$wBQi7K6V=Jkrz`1Q0nAzqdpleJ1WWI&`BTj z5J+_qdvEXe2P(rQSwB68bY{kJ&~cfH`c4{gZ_<&72aD7R&rB7bT?B;v4;4$|_n^Z4 zW&JqRlZ`J%VlRT~sDY80By}myxP??jBTbTxuSROjhw3PTnV2MvlX)ygl~(yBPbZ?v zKJG##t%01FD3U7EvF`NLW^z8OiCTr2R*`X6Xi%v|&PF$rPDzr6kV|3{kapo<~? zb7w3#Oql=NS(2-pcjccFj~4ge{r_`kY+#C(mocFR-d}~r3489PVEe>YIyy!wyTuua zshv~`88!Uz72Czk!qtfzPM!6u;EfGpI0RZ+`a(i-EOCjNHj#6f@Lz>EBYNGp@56}L z4tFmz^4v{ZcF4f1te2jbp4(TyF`xG%7u4?v#yzT_F};s=ieGIoDuDu@?F`}Ugg_+G zft^O@I1&fMeZ%j&#KQu2v!wKnMrbL#XJcJ~<)Z@#&b@k}FXN8Vp)Zr?FB5nECT~B$ zNRn^E%#MfH-=)Dg9(s&I<6!RC-h@fF!pu{VjWv5wC-0QOP^UZ;L*w4>rNQDNvE$(1 zX##nP@~{L*(l*M>exHL0QwS5@gMjb{Z`D`ZG);4F_hx7oAY-t+wA*hGuRp>jKER z8e|3%?I}`Po0D6_5LIj#sVxS0L6_#S?#P@nypoiCH>1 z<^f92E{#VsXSMlMxDj%emVn;$wJ}@K$7~cWG;9UN26-lmuJY-+f)yTD99--fL;^Vu zZ8J%1Tnr|~p5=fzlg0e0r}P#o6KBq8NCam4*|x&=NznQEM%@@YmLztsJ%!>}CPbQKD2NCF z3!&=K_T?(>e2JM~*oJ-JjEi}8!F;JyxF+LIs(9uGUoJXX5kPbRe&&)8nr`O;m*^(N zxFjYTPcQssP?6Q*$7Y-n>sM**h+V>Hd4?Iv>4KgseT-Eq@m>X+BQ}(+Y!l+t<*Wwvy^9q&@Gkc?OsmOPqeMu6Ah{M z$h;sh_2gS(qyJ85_PW8Q@A(2#L3**F75aqPLJ#v$y=5NvPF;jlCLL`=knl`4DLI#f z>wI`Hg{sKVJr;dA5exZWTrS;ZEI{y3>r8s#TuSg<3>2uR75HWeTE%YCa(kZ+09Po^ zRy*4MNj35;q)W}W=P+lGTx=jA0WJR-iPWW-tUrSM zP)2e}`l3rn4KRPDg|3@ausdutIBT^yYtSjkY_fjvkAJG7fDJ%ob-&$J>brm0q1A^W z)>nW=*Z74>0|AQ~Tc`m&^GK9NHx!G86PQ+|^nmic)JMeRVXpV};$mmycMu+S-H-zP z7x~2lZz!jY>WEPY$e~f^bSGNTx?8*`gARL+pWR;b9_V4BRwpkng@?SeaEH3_X|Wqh z@oLm~@32u$w^$Tm)|MbMYS&zx7V?4(zRFo@YrF+r`?tJ)c;S-PdJA~3Epq!jMXV}sp- zEUT>=bKvgEP4TGedJ@_|ulr#Uy|5rhoV0(C*4?X?G)D9I3zHp!O@-&zY+#GR^+AG+P)*niRpP{5J2Ce&ohb`;DKf$Jh zyFyaxWLJ$J^5c_L$#h*ZqPIVGn=D$YPt>Z*9FVnC_J;<2a=uZ8I<`jT9X9L7B!)(X zYUSr7d}X6LJlz=~>!5pB0pF_GtMlitB5t9YvEglAWZF#O$Mps@r6+*s6Ij9a?Zimt zHW%-ij?nXZT*aI4)K+P;&Zu+O;SzTTL){ILrZX>(@BQdyPi3~GN_W^Ab{BVfFTcUR zvKousEHDpFOJN{~8GE{RU0+>8p%d4o0IuS>$SY?B8@jtAcd+J(tu0Wng@Nt59I><~ zxCsR3;Xz35{Ly7=w#fPWcsB+c3o}OHLUl~;NcCij#hi~v_GX~%F1l{2K`O^L>K<%J z8-O>Q;VXbt?oE8U6`#RCxD_&<)pM_i2I!WiEC=sdX?5@fEVSbK#X1blgFyT z0`FQ@&mD{)N>~(edyMV_Q6HW(+}n1HwSu++qnbwOus|!G_<0_=*u(QIN#^!x9$1#> zpAcpk6|aAr9d;Dj*4KS>&2sDSU!@F=3=%}A;QHm93!ynHf|n&Wp-rjIkGMxTISNqO zWk>uQ&BQa?kZth`a7LKMS5`l^O2)A;SlKVG=nmnZRmGGjvCmwcE;gb~d*%a`Vp@`L z7Nl&^aKyGiKv!9dP(r9jR~*CY3iMoV?WmoYRzdXg7E~Ec*%)W6r0vWsRN{>; zNmw0%lh8#eqF!{zZDHcPP~2W!{6l&{j_T-xGu5bVuO!BBlqB)Vs0nZ?Y+Bu~u4!CT zbY4|-UmYC2N@mm)h$`ccis1&~TiDe!t(w(wk%$gS<{?WB2NX%{m%q#u0*@Qs&qwjM zLwxFIp?@-@I&XuKtW`PjTRXuN`BN(n>ge;qqG!ulQ+PK-INF9rJUT+omPISdc7VMc zikdChL3+XzbbOm~!ecHI3=F0$wWYPNQ^xk8##8?}2eqp@`zClqs}l2=eZ_@Qzbag~ z{u}DzdfE$&OQ+eTBnU5g1Zc&3}f?3^>kzT&1)H&*^MlW5y($h7)9Rst|z89 znSDj0zQAT=>BkwBn^SD9*R=izjhTL6R;>K0ZD*rZP0$9t%+d&+Y34}vClewv;|B&z zWf<9oh0~bMF7+7Fln+Z!d**IrANrrQIq{7szoWgWi}-!SE1q`TM8ZA#Y(+QbJ8+o?xI@!5WHyR=atO7Dd;{6Zr*Cxt z!(hiovzcP}^zaSz%zjhn;t*=74%Wx4*6=-rZeLam*bj@D+fxufZmgUeDnavZUyC%8 zm30lQ)XYmB^nC*V@3{A`jYM*M!&~NG2MR2h(!Z2D1z3{7098`IE*36|l7fmOBdscH zqdIGIGdpV=JDZxLJ2Fbrr7jD`f2z2PZr8bgYpB4%z;MBmY6tLvc5yxSkYs4VK895M zQ0X9f5fHQL(WvI8Wq=S#Y5?YUP2%qs8#j$RJRDtB4MN)=4N_4f#o6)eVU8&1HbL$9 z1Fz%7^S(ce)Jbd!4xOV5{JcRqSt-K_dJPr=b0z8XbDKUfixWC@FuIdoN~!vKf#Ic+ zXWuX`=mvDtw&fWNh$gUa?qW(pvysX$&A%s6cMy{h7?C~iA@6?0I!kQymm6@TK#JE< z>d5sL+MKBN@5N)iSc`j)JosLv#!@A|KTd(^gU#;e=NPC{@yBMqJsFA>E;P(jS}`$TLOwkMOr^YVnf_U&s?gX^tD;6zLMB|Ly5{z$U6tRC zugMJBZHve5$7=_sIA@g7$s)=h z;M)gKLYQRYv=0B1DeX&<j@jE;xG{D9C)oam1kuBF{m&!zGDZu@a11TCZE8B)$c49KS%l$zw&z z@EZ&z`ROxKSWH+%M$13nb6QR;`T4eva<~E!Sfh?GV&vQ^fBtsLXg+>`ACE+MQ%4A* zKM+t8ZW32&v3DoV8*({P%fHvp!EtFBeRS2GeB?6BcZyM*@+?qn>6Q-1vFR2sdmKTK zbL&(F;5>HVuU)t{uWqY5Uf!eJd2T;6QHrq&?4vk-wfG@QqGD)EjFsIl5j&Puy>wP1>i< zIc;~?{8`;AV>5;D9}9&|BQ?VINfYidLGr_9)o@`omImX|Bh|P|=^8or#+2K*aWF%6 zZV9o99M+>0Dlu(r3bh=jtC5b=2xGv&`+Y@*tZ=$L!<=4wfu(l2p{=0=0}~(T7Q7gp zh0%7rHck|g!;!L#OJjORToexM6+}mFjLj5(#w zy~a-W>&@ddPS!Uqdk&9G4TF2*a(e3)ovtd)EGJ8B!*NdfpA22q0u!d{_-;KL@R{4A z#(L|~9uf^h#~DpgS9xR{+nVMa+U@KZc2(9a`@`AABT7UOMx z@2_A0=FH%xv}o%T77E{h)p&h#xca-uDvSI-4K}9JT1F2WY#YCwp%x;hmm%NNDhH1c zHAiKqKvKf-5pq~@Vlq&N&vw(q%jMw*3`77%0#+3=>Jg|pQ6xGW!cZ$(ldMNCj9^;P zb{oKn;L$0jiK`&u!fos8{D&%^3Y!rEKS+C+eW7L{(sY0?gCZ(4%*eaUc(#PC30d)} z%%`-q2@~Dk0vls5+>G?;OuBg_k~(Re7`p|`p=J_$BH@FxMH2{PT3BJzwSv69ZWY=| zx@;N(QhDpywvS>@@XEwb)kvsqhsrMs9siLnMs&eDZsz3gjLS*EksRuri&h=5x?*wz z$jr188N16#Z7EoaXL8*PU19^Zd*95DC4u-~TBXs@Tgxf!{0u0u8Z%SIazr=5s|9(f zhbPXZTdgm-S%K)=YDo>r42|af*lY|Mnsg-Wb1&ebQIlq- zaycH9NkiMqWwU4M^cMh^ChRDMECCnlrKiLKk$f!ZAG0?ZkPN3&lP;l6&dLjkW(_~I zkEr~oRVx{Bg@;5&LLAV~u0=&1qFp7jREFSV%BXV0J`2 z*#8Lt4Og&luOf#^Q#VYa=6UVsQQN2Thq^IIyp@Gtz|dg1Jqz$`#;BbR1L7 z_U(HY^TYnBY7YxygG8>J2C zzC`{a+yjDTPsK6?IUGTT2Sp+TeV^m`ms+Lc?76_v;Q`^6m#=%i9nKuX>nU=suj-;5w>`lOioG}O8o``|jaj`71%Y_YFc||lt&ZL-2PjT`p zXl6|nEl4l#4;4PG59kmewMdI{{B}bHPVD=<2?K*}zljO!Z#aL-r5s z%iXqsSRUG{NmX~8C7^!1lFJ@YdQijDtB8@DvFBsw^v4fCr{z9NdtwNRiv-U*us{|b zRRUUVo6akBtnGtzrqs&92mCOcZq6Id52E{<*f#f1UtxmY-@M>5YP+8xAm}vp84;0$ z$*g!s@nX3ARnGbX$f;^1Avaqy$gsK$hI!ZU0@`Im5o9*GL2NGU8eSS^k|d^H0XMSgC3xc z?~1rnc|QfyH&|!=k!d#kEKd+N(ZGv=;`WV4YS4)TH?%P48i2bM0h1M#wRO+{2-Zfr zr)yMiGM7B2fvLXsjWObRMOHBrohU8w;dV3BAb{BGAyDgyWXhV7MfOiZ?^X+q22$yx z%n~J9j-LiWi#77@$PGFk?!4s$Sp6>RlR}1#RC<(T^YC=!Kb!d3{+9jeIDqxanO1VJ z?{`up^>%(vq{{NyeI7ef6DxuW|J$kCF~53_zg$D-yZ1a3FsSpyq5x-7s16VFDiOgF zwBK3p>X`AJ9I~_j2lmH4XKnG5E3l;KFK0;A8RzQTXk5ni5N&X1uPln+x7?CqY?M`q zXLVL5bJ%8pEz32%5w9m!`?mcke)W#ibAC|g0+Hr-$+TpC?7cT4z>8)_j}&NP%S+72T2fgmh9!2IHDBEg zuLuqX_lE7vp3=@I2m?Z|yi(ny!0*H|J3DfO8YCNl5tV18^13Z-wQ&s2$Ixm=y>5dL zI;8k-@I2y!GO#BMlSkaCQ9%D%?uzMI_ei0#enm(`jl~YfT#EF~!i3t{XORTGbQI)= zBQhr*)3a$MaZ8*=$~jrC(~$a2+y~qdud(22wEs#j~H^W3fqKG#f z68Pmg3ms-QyASEQU`;_SZZrP1nF3+)Q1s65dw-x(i48zmppZhpw(5c%5T>bnpLg#O zf>kTAa_#p^g@}Gsa=!}OzLnk9jHRya8{&x6DVx^DhuTz57XHX?mvX8LJ7jFl{ z;S25j;b!b@+MNws$HI`M&UdfO*u=K9X2S=e8r*1@`lT#yx?9|id9KXKogU6Z+LXFw5WqtGI@_(=>!L|Q@oC(HV+CpT z&4n)bU9QZ+WsKx^`acy2(Eyy;i-HYN!LG2`1b6G0x!cPEzOeH7TRHo0mGj_uxm)`c zvrLoNNgCh99~&4x68&b%7dUh-;8VRLkGDfVJA99pr=%?DTqk6-c-Vl!rKlxoXy=pW zkNB~^g21N2uoGCyTz&f7SXHpS7|vMk1@&Q1CIiK93pl!RTJaIUEh`2$H4Xy>CRa=y z=>|=49ReY_>n9B8^REA|j4J_$YW@FXUm~*0E(R?avXdP5CfS|EZS3%aj~T9rve5t0^r7|8r{#ci+Fu^b-7*SF#6Nn@2i{DaX%t zJMt(SjBS+EptEN4Irc~z643@Sn>;Hf#}}r%c!t;Hl8zY;l(Z^c?K(_XocCt5ffe@v z;wEH*i$uPvWXID8QRz%_C*$R>TLLSb>V3))%=H6*mZWW~y1M3=>QoF5XAftGH;qPR z9ubWOFtj*-mkS_QqINq08=bxak22$lCl@UvGuuII~lfl<25HE`%~>X3+DBD18(ytznY>n>(CwbW|M zlXN7QPS*IWKC((LjH9V0yBYgz;cD&VjWv;fnCf}dbXffFjQNqgo+^Ib=EM4VVE>4_ z^P3oPa1Hu?(X)1P#^|*pndS$VTD3sdROzq>XQ{TF{D>hVqPd z^mEL~4Ei!2N>RDn1Zp4!(2OOPt^Y9Wvp7y&R~eqq5~}Qw%JxgOZX35%Dc^gWCo53p z$=Fm}IULo4rk&~6_V4BPS7+bQ+poKOX6*brN!mYXi=&#FBJ7hsvY=67lP6L{WM2bT zu#pd#ns)|0QgX8TqEc4S4GFsfMT^ivVH)vR$HR8I?9Wt z%iV|aM;L=J6vNEv99vlt8;vnoilCh*-vJ`v6jn;BC9_%G?VRFlffKAz2yvi*+1Y)M zelRkyE!;khlCo2OL{Uvk7IN0W$jO~%V4VErk1I_vRZ??>T>^!GQ|Jucaunrv%aGKN ztIC=Td*5&FQ(e9{=+aCvF{A0ax=OZRq$8=FCg*rp37tH&xdgjmF)Aj0XVdT8tB*># zd7@%(qW71cI6kAWdT(IbntG=xQ2V>{;w7}@-_@lL>+)IQmz4}t-uuD1^QT~aho85J z31PAu!F7;^C^Km2Qlwa3Z}i>kGr`KuAgswY>iW4 zAeuQv!zng$nRr%t&~aaq_eed>03CYw3OeV_vYRmfv3{Vq@1s&F5Fi||m;-mcZhJ$HAW(@Z}&+YrKexO=>%~pFwhca1zv>AHztqc6%IT}RY zM$oBCtZo;)FDo>EM<1sg?z44`$qaWz&2>!jx(S{4$E5Ot|@7=(%=FOIc@ll@W z&uQY`)NoXe+99uzY4BWzP4tH!!)s(%!(F)swz%AI%KzDi2LB!`unstRf0Y%ufAT_1 z*YZ9w1O7#pg`aE6BMKO-`waLZ3kIC&5(eYzAJ818%*UFOJwsQkZgj^_!*2}BCfmzJ z3Jmxa#e9V8aZZZ3Yu_`cW_0E^NN{*G*b{A`gL*a)k(Wo7mK-b@6PkOO3a*?iv$xU5 z;f7r|rEgY{Jc}BAi4c=qnvvU8SPF8eA5uO(=vF4#6Q422W{jkf45g1{Su{*`7eR*> zHdb~%=kL(Zel=YLufn|1wrs2RQ#|+aCVLhtn^}{1a>J4#tJ|KvUuc84nZ7$lqB&)* zN=$Gd{)t_XT&OFXdyoe9R{TfijgT9TSh#+ORCL9BwONQ2EJ(fUt2;JPg|0cZGu;oh z9E4JAzQpbJdM_TccIU&W-g6H-|50?E*YlptqL?p9@@BQ$LvuCe+=&AcC9Jd~<>qM^ zAnq1&+Fo1gLAYpZ&m$*hikM_`VW;p-!QgfGkz&UPDo=MqDQe?}XPF)s<73ar-KeX1 zaAzE6k~g?lddK)vTURD5{B%Ozr8Jaz8-r;T(YpFJ;<#|hKtr(-*|`c^;q!L<}@#g-q#rr|+f zdhf*T?C?ekcwHWeqAnpjf%KTr-DT?=(LFYN8O+`7i_2z6t4v@5PX zx9C((IKbm2VX~26Z(2x+EAgoZ*OmM!e=oQA33ZxQS*dCKib;g|kX+)2>~-P`US4k+ zLXYb%7CtstTI-Q^-Wp6FB^xQJA9gKBX|ImtOa@TYGD{sSN7g5m-!M3``!H5huML}guhyoj z_&m9I0>1UP<#QBnm*vYCC0br(7x(E08dLI8z4lH;fzv2y_V8F|>CM{DwSP1?uE)do zj@q5bqBf+dtb|rxn%|s@$gx4g4$`mOTfcyHqZ5|JkT8IoB+0wp{8G(p)`na+WH)8T zhWpwFnYQh&2a|z06oPiR&oYvAQYK7?l*-8)jMAN&IQzrvZQ#a3R1{6_vKcEyi;{7y ziH&1^7ST=Qt;S1Y)d|dR=N^SIABLIs!;QmzHLg6Ld$PwIrCAXsqI&_I0{ea>WnzD* z*(QW2n0v9uh4i0}wJBg4F?v_>-1M{5!>G)1gVdR)I&NL!Yr3k9t;?qlG`d84Ugpoq zn=oUe3YZSLOWrL?BZT-en7tESpBiR#H2Bt5T|b+akLyLreDg5#*ngW~L6KR;g~-tO zDDE3Aa5XIRYyZ!yDSp|2tnSh(n@!Yw1#`N}^mAMf*yMM;<9fA9&Qzh8;v>+SgK@?YY1 zIBZfQnTRjPvw924Srv@!BimK-MQ?kv?q1=ysw(d}>w|Ik`PA29g2Vb|_F)k1OGLs! zK9Y-Ehh?*>MS$`-dnjzK27a6kL&o)S-&vq49(2Ax%z+ zbM3}iZ-;Zu642~RuZMNj?k{(5kUE_jJ9Dw7Z;v!9uTypPOi-8tzv|uk(T3#!su)x)*DoS8AuL9k)WtP%+rY8L~Rd|lQWXSZ+ zcih&zap~@U1G7Tbj?)p1ptXX)&kIM+FQ%|})< zsbK6CQHgWwr83veIA1W7ly!-J&&Nk$Sj!`qs(XpaoJ*(kL(E>O!vSso&NcL1pZ#s1 ztc0gQqB7F*{-lWE3(KvjPST^LfMr*NQU~Ani__x%@h@68GY-V|aBB;~!Q&$Nq=79a zy6Vku)Z?G?s|!+iZu}=Oh`+s;m0F$L)nD1JKtU~F%z6ciU{Oa5o>&AjHcrA_HTfhb ze+3My2Tn{Kw|<H=otH!fD_%J01 zgMqoL(82ig8wR6?t*IVBxqqdNH2CyDps|siG&fX~2q0=<1{dZ~0XvP1kRhQNZ(tyX z3@vPofKxXOFP<55Hn*f26jlTC2*H5euOm0qnFPB~h3xyt2fjPGX6^fHuSb)1?XMvEG6F=hr-JS#_<|TmE z3xM3BkORLLRl#8V1fWZoK@ba2NO~GC4uJ_)DiMH%q<(s<1ocf!E52`XWT3(pHd?^q z8r#MHjtIV=ey>*fztW|0U`0;AtCb0oH4Nr*`UJL_9qd$b0$FYqNZ~s@*RS5s^A~S0 z!4E7M48}tMHq?N?7g=b2feA903BZfU6W^hPW=1gL`+GLwXGI?t-@sNKJSeFN4!Oy> zU^V#)RNV*Nlq4W?(1XYf<#jSV6gE|RX$#D&i)&N{~3b+jm!;Au?7S32#777 zAm098(D_?n^c}8(KgsaL|IT)B$C{r9ezuF*oSZKX9=GjW?8W|YaUwW%z{!dhkqR zcE613ulXTELNDIX>i`-0d$}QcKls9R8*tpug1b~I{OdFLog2#cCxB>Y2k!NVpU^@M zPPjs~6+1=0#0m+%?uH!3$&IR|{= zx(tDYNVRC#!2}HlkWJ(_26T?K7-~gqH62j-`a~96swaWZ^A87Ql?cGTB|m+{4>LJ{ z?%;&jC2rY2%m;zX;76FC!XM>Oh3a-D2pFR(6lP3TgQ~>M|ioaaAMR#TB zIo? S4DdGa0Z-o0lMA%qdH)9}vIF`6 delta 20795 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 F{{c&BRiyv` diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8308df30c..6e00d9230 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -4,3 +4,4 @@ distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-bin.zip +distributionSha256Sum=010dd9f31849abc3d5644e282943b1c1c355f8e2635c5789833979ce590a3774 \ No newline at end of file From 14921e783f66b8cbdec428e0bc8a57961d9cf62a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Wed, 29 Apr 2015 22:56:49 +0200 Subject: [PATCH 18/71] Update wrapper to verify local cache --- gradle/wrapper/gradle-wrapper.jar | Bin 53322 -> 53323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 497f80dd6b5dccb96a97e72c70967bbb10242a11..cb5ccd9f196839dbb7129167dee12924f1036d80 100644 GIT binary patch delta 10511 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 b{A(JRRRPcM0d@6|fjH+-p{aWRRPg@*LnMrX delta 10570 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 Date: Thu, 30 Apr 2015 00:59:57 +0200 Subject: [PATCH 19/71] Update snackbar --- extern/snackbar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/snackbar b/extern/snackbar index 97cda0806..12e1e8b7e 160000 --- a/extern/snackbar +++ b/extern/snackbar @@ -1 +1 @@ -Subproject commit 97cda0806d9ead9a3aacf3d4f7275ac0e38d960c +Subproject commit 12e1e8b7e88a09249aed68a48a6c4d06c401e00f From e159e619d299c0e91f3f9527186e50649657dd73 Mon Sep 17 00:00:00 2001 From: Daniel Ramos Date: Thu, 30 Apr 2015 00:44:01 +0100 Subject: [PATCH 20/71] -removed unneeded inputType attribute from xml files (+1 squashed commits) Squashed commits: [ef54e68] -code style fixing --- .../keychain/ui/CreateKeyEmailFragment.java | 20 +++++++++---------- .../src/main/res/layout/add_email_dialog.xml | 1 - .../res/layout/create_key_email_fragment.xml | 1 - 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 035945af3..078135772 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -89,9 +89,9 @@ public class CreateKeyEmailFragment extends Fragment { View view = inflater.inflate(R.layout.create_key_email_fragment, container, false); mEmailEdit = (EmailEditText) view.findViewById(R.id.create_key_email); - View mBackButton = view.findViewById(R.id.create_key_back_button); - View mNextButton = view.findViewById(R.id.create_key_next_button); - RecyclerView mEmailsRecyclerView = (RecyclerView) view.findViewById(R.id.create_key_emails); + View backButton = view.findViewById(R.id.create_key_back_button); + View nextButton = view.findViewById(R.id.create_key_next_button); + RecyclerView emailsRecyclerView = (RecyclerView) view.findViewById(R.id.create_key_emails); // initial values mEmailEdit.setText(mCreateKeyActivity.mEmail); @@ -100,21 +100,21 @@ public class CreateKeyEmailFragment extends Fragment { if (mCreateKeyActivity.mEmail == null) { mEmailEdit.requestFocus(); } - mBackButton.setOnClickListener(new View.OnClickListener() { + backButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); } }); - mNextButton.setOnClickListener(new View.OnClickListener() { + nextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { nextClicked(); } }); - mEmailsRecyclerView.setHasFixedSize(true); - mEmailsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - mEmailsRecyclerView.setItemAnimator(new DefaultItemAnimator()); + emailsRecyclerView.setHasFixedSize(true); + emailsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + emailsRecyclerView.setItemAnimator(new DefaultItemAnimator()); // initial values if (mAdditionalEmailModels == null) { @@ -133,7 +133,7 @@ public class CreateKeyEmailFragment extends Fragment { } } - mEmailsRecyclerView.setAdapter(mEmailAdapter); + emailsRecyclerView.setAdapter(mEmailAdapter); return view; } @@ -216,7 +216,7 @@ public class CreateKeyEmailFragment extends Fragment { } }; // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); + Messenger messenger = new Messenger(returnHandler); AddEmailDialogFragment addEmailDialog = AddEmailDialogFragment.newInstance(messenger); addEmailDialog.show(getActivity().getSupportFragmentManager(), "addEmailDialog"); diff --git a/OpenKeychain/src/main/res/layout/add_email_dialog.xml b/OpenKeychain/src/main/res/layout/add_email_dialog.xml index 2de657a40..68d895145 100644 --- a/OpenKeychain/src/main/res/layout/add_email_dialog.xml +++ b/OpenKeychain/src/main/res/layout/add_email_dialog.xml @@ -21,7 +21,6 @@ android:layout_marginTop="16dp" android:hint="@string/label_email" android:imeOptions="actionNext" - android:inputType="textEmailAddress" android:textAppearance="?android:attr/textAppearanceMedium" /> diff --git a/OpenKeychain/src/main/res/layout/create_key_email_fragment.xml b/OpenKeychain/src/main/res/layout/create_key_email_fragment.xml index 7b9cffec4..17cfe54ac 100644 --- a/OpenKeychain/src/main/res/layout/create_key_email_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_key_email_fragment.xml @@ -32,7 +32,6 @@ android:layout_marginTop="16dp" android:layout_marginBottom="8dp" android:imeOptions="actionNext" - android:inputType="textEmailAddress" android:hint="@string/label_email" android:ems="10" /> From 7c275fed9d93e0c45b2fb00d94bde702b44c8811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Thu, 30 Apr 2015 18:45:43 +0200 Subject: [PATCH 21/71] API: Allow selection of decryption keys when decryption fails --- OpenKeychain/src/main/AndroidManifest.xml | 5 + .../keychain/remote/OpenPgpService.java | 34 +++++- .../remote/ui/RemoteServiceActivity.java | 1 + .../remote/ui/SelectAllowedKeysActivity.java | 115 ++++++++++++++++++ .../layout/api_remote_select_allowed_keys.xml | 38 ++++++ OpenKeychain/src/main/res/values/strings.xml | 1 + 6 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java create mode 100644 OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 8c66176fd..6bc61460a 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -740,6 +740,11 @@ android:exported="false" android:label="@string/app_name" android:launchMode="singleTop" /> + allowedKeyIds; + Set allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp( + KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg)); + if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) { - allowedKeyIds = mProviderHelper.getAllKeyIdsForApp( - ApiAccounts.buildBaseUri(currentPkg)); - } else { - allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp( - KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg)); + allowedKeyIds.addAll(mProviderHelper.getAllKeyIdsForApp( + ApiAccounts.buildBaseUri(currentPkg))); } long inputLength = is.available(); @@ -575,6 +588,15 @@ public class OpenPgpService extends RemoteService { return result; } else { LogEntryParcel errorMsg = pgpResult.getLog().getLast(); + + if (errorMsg.mType == OperationResult.LogType.MSG_DC_ERROR_NO_KEY) { + // allow user to select allowed keys + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_INTENT, getSelectAllowedKeysIntent(data)); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); + return result; + } + throw new Exception(getString(errorMsg.mType.getMsgId())); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java index f312c0d44..5facde64f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -45,6 +45,7 @@ import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; +// TODO: make extensible BaseRemoteServiceActivity and extend these cases from it public class RemoteServiceActivity extends BaseActivity { public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java new file mode 100644 index 000000000..767106ff0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java @@ -0,0 +1,115 @@ +/* + * 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.remote.ui; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; +import org.sufficientlysecure.keychain.util.Log; + +public class SelectAllowedKeysActivity extends BaseActivity { + + public static final String EXTRA_SERVICE_INTENT = "data"; + + private Uri mAppUri; + + private AppSettingsAllowedKeysListFragment mAllowedKeysFragment; + + Intent mServiceData; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Inflate a "Done" custom action bar + setFullScreenDialogDoneClose(R.string.api_settings_save, + new View.OnClickListener() { + @Override + public void onClick(View v) { + save(); + } + }, + new View.OnClickListener() { + @Override + public void onClick(View v) { + cancel(); + } + }); + + Intent intent = getIntent(); + mServiceData = intent.getParcelableExtra(EXTRA_SERVICE_INTENT); + mAppUri = intent.getData(); + if (mAppUri == null) { + Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!"); + finish(); + return; + } else { + Log.d(Constants.TAG, "uri: " + mAppUri); + loadData(savedInstanceState, mAppUri); + } + } + + @Override + protected void initLayout() { + setContentView(R.layout.api_remote_select_allowed_keys); + } + + private void save() { + mAllowedKeysFragment.saveAllowedKeys(); + setResult(Activity.RESULT_OK, mServiceData); + finish(); + } + + private void cancel() { + setResult(Activity.RESULT_CANCELED); + finish(); + } + + private void loadData(Bundle savedInstanceState, Uri appUri) { + Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build(); + Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri); + startListFragments(savedInstanceState, allowedKeysUri); + } + + private void startListFragments(Bundle savedInstanceState, Uri allowedKeysUri) { + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; + } + + // Create an instance of the fragments + mAllowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(allowedKeysUri); + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.api_allowed_keys_list_fragment, mAllowedKeysFragment) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); + } + +} diff --git a/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml b/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml new file mode 100644 index 000000000..e052ff333 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index f72bdb7fc..d274a5ca3 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -521,6 +521,7 @@ "Please select the recipients!" "Signature check failed! Have you installed this app from a different source? If you are sure that this is not an attack, revoke this app's registration in OpenKeychain and then register the app again." "Please select one of your existing keys or create a new one." + "None of the allowed keys is able to decrypt the content. Please select the allowed keys." "Share with QR Code" From c88d2e42b747fac700ab6ac47beeae91c76bdbd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Thu, 30 Apr 2015 18:57:51 +0200 Subject: [PATCH 22/71] Fix key creation string and display --- .../keychain/ui/adapter/KeyAdapter.java | 17 +---------------- .../ui/adapter/SelectKeyCursorAdapter.java | 2 +- .../keychain/ui/widget/KeySpinner.java | 14 +++++++------- OpenKeychain/src/main/res/values/strings.xml | 5 +++-- 4 files changed, 12 insertions(+), 26 deletions(-) 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 f09dc1a4f..eef44a94b 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 @@ -173,11 +173,10 @@ public class KeyAdapter extends CursorAdapter { String dateTime = DateUtils.formatDateTime(context, cursor.getLong(INDEX_CREATION) * 1000, DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); - mCreationDate.setText(context.getString(R.string.label_creation, + mCreationDate.setText(context.getString(R.string.label_key_created, dateTime)); mCreationDate.setVisibility(View.VISIBLE); } else { @@ -281,20 +280,6 @@ public class KeyAdapter extends CursorAdapter { } } - public boolean hasDuplicate() { - return mHasDuplicate; - } - - public String getCreationDate(Context context) { - Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - creationCal.setTime(mCreation); - // convert from UTC to time zone of device - creationCal.setTimeZone(TimeZone.getDefault()); - - return context.getString(R.string.label_creation) + ": " - + DateFormat.getDateFormat(context).format(creationCal.getTime()); - } - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index 1ccb910d0..68c59f647 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -141,7 +141,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); - h.creation.setText(context.getString(R.string.label_creation, dateTime)); + h.creation.setText(context.getString(R.string.label_key_created, dateTime)); h.creation.setVisibility(View.VISIBLE); } else { h.creation.setVisibility(View.GONE); 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 fc5ecd76a..aecc81604 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 @@ -26,6 +26,7 @@ import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.support.v7.widget.AppCompatSpinner; import android.text.format.DateFormat; +import android.text.format.DateUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -167,14 +168,13 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag boolean duplicate = cursor.getLong(mIndexDuplicate) > 0; if (duplicate) { - Date creationDate = new Date(cursor.getLong(mIndexCreationDate) * 1000); - Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - creationCal.setTime(creationDate); - // convert from UTC to time zone of device - creationCal.setTimeZone(TimeZone.getDefault()); + 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_creation) + ": " - + DateFormat.getDateFormat(context).format(creationCal.getTime())); + vDuplicate.setText(context.getString(R.string.label_key_created, dateTime)); vDuplicate.setVisibility(View.VISIBLE); } else { vDuplicate.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index d274a5ca3..1a862de7a 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -137,7 +137,8 @@ "File compression" "Keyservers" "Key ID" - "Key created %s" + "Key created %s" + "Creation" "Expiry" "Usage" "Key Size" @@ -503,7 +504,7 @@ "Delete account" "Package Name" "SHA-256 of Package Signature" - "Accounts (deprecated API)" + "Accounts (old API)" "Extended Information" "Allowed Keys" "Settings" From 5ad50b99a21642e466cad0d9e23976793f7cf765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Thu, 30 Apr 2015 19:02:33 +0200 Subject: [PATCH 23/71] Remove time from key creation display --- .../keychain/ui/adapter/SelectKeyCursorAdapter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index 68c59f647..a6cb52977 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -137,7 +137,6 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { String dateTime = DateUtils.formatDateTime(context, cursor.getLong(mIndexCreation) * 1000, DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); From e91dc022fbd5d616c2ac1b5bbd47a693b9f8f8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Fri, 1 May 2015 14:36:22 +0200 Subject: [PATCH 24/71] Fix nullpointer in EncryptKeyCompletionView --- .../keychain/ui/widget/EncryptKeyCompletionView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 525bc26ca..df6b82978 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 @@ -136,7 +136,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView where += " AND " + KeyRings.USER_ID + " LIKE ?"; return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, - new String[] { "%" + query + "%" }, null); + new String[]{"%" + query + "%"}, null); } mAdapter.setSearchQuery(null); @@ -156,7 +156,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView @Override public void showDropDown() { - if (mAdapter.getCursor().isClosed()) { + if (mAdapter == null || mAdapter.getCursor().isClosed()) { return; } super.showDropDown(); From f3870cee290a7f8f996c14d457cba3be4127cd3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Fri, 1 May 2015 14:44:40 +0200 Subject: [PATCH 25/71] More nullpointer fixes --- .../keychain/ui/widget/EncryptKeyCompletionView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 df6b82978..63a1aade9 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 @@ -156,7 +156,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView @Override public void showDropDown() { - if (mAdapter == null || mAdapter.getCursor().isClosed()) { + if (mAdapter == null || mAdapter.getCursor() == null || mAdapter.getCursor().isClosed()) { return; } super.showDropDown(); From aa68c41f5a3867d98c2539698546d5ff6de8391c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Fri, 1 May 2015 14:48:57 +0200 Subject: [PATCH 26/71] Fix yubikey strings --- .../main/res/layout/create_yubikey_wait_fragment.xml | 3 +-- OpenKeychain/src/main/res/values/strings.xml | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml b/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml index c7f9821eb..4e4b53118 100644 --- a/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml @@ -22,8 +22,7 @@ android:layout_marginTop="16dp" android:layout_marginLeft="8dp" android:textAppearance="?android:attr/textAppearanceMedium" - android:text="Hold Yubikey against device dawg" - /> + android:text="@string/yubikey_create" /> "Identities" - "Yubikey" + "YubiKey" "Linked System Contact" "Should you trust this key?" Proof verification @@ -1280,11 +1280,12 @@ "Serial No: %s" "Key holder: " "Key holder: <unset>" - "Yubikey matches and is bound to key" - "Yubikey matches, can be bound to key" - "Yubikey matches, partly bound to key" + "YubiKey matches and is bound to key" + "YubiKey matches, can be bound to key" + "YubiKey matches, partly bound to key" + "Hold YubiKey against the back of your device." "Import" - Different key stored on Yubikey! + Different key stored on YubiKey! "NFC Error: %s" Default PIN was rejected! From 971ceb07d4fd8cba92a1ec32cc2a163cf11c3559 Mon Sep 17 00:00:00 2001 From: William Faulk Date: Sun, 3 May 2015 14:06:01 -0700 Subject: [PATCH 27/71] updated robolectric to support new android support libs --- OpenKeychain-Test/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenKeychain-Test/build.gradle b/OpenKeychain-Test/build.gradle index 1c87fcb4d..2bf35b3d1 100644 --- a/OpenKeychain-Test/build.gradle +++ b/OpenKeychain-Test/build.gradle @@ -17,7 +17,8 @@ dependencies { testCompile 'junit:junit:4.11' testCompile 'com.google.android:android:4.1.1.4' testCompile('com.squareup:fest-android:1.0.8') { exclude module: 'support-v4' } - testCompile ('org.robolectric:robolectric:2.3') { + testCompile 'org.apache.maven:maven-ant-tasks:2.1.3' + testCompile ('org.robolectric:robolectric:2.4') { exclude module: 'classworlds' exclude module: 'maven-artifact' exclude module: 'maven-artifact-manager' From 4e425495e6d69cbe54840f926e659eb12e79eefe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 4 May 2015 13:57:43 +0200 Subject: [PATCH 28/71] Apply patch from https://github.com/open-keychain/open-keychain/issues/1240 with db upgrade --- .../provider/TemporaryStorageProvider.java | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java index a65d222da..6dd4a1633 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java @@ -31,10 +31,12 @@ import android.provider.OpenableColumns; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.DatabaseUtil; +import org.sufficientlysecure.keychain.util.Log; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.UUID; public class TemporaryStorageProvider extends ContentProvider { @@ -44,7 +46,7 @@ public class TemporaryStorageProvider extends ContentProvider { private static final String COLUMN_NAME = "name"; private static final String COLUMN_TIME = "time"; private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/"); - private static final int DB_VERSION = 1; + private static final int DB_VERSION = 2; public static Uri createFile(Context context, String targetName) { ContentValues contentValues = new ContentValues(); @@ -66,7 +68,7 @@ public class TemporaryStorageProvider extends ContentProvider { @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" + - COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COLUMN_ID + " TEXT PRIMARY KEY, " + COLUMN_NAME + " TEXT, " + COLUMN_TIME + " INTEGER" + ");"); @@ -74,7 +76,17 @@ public class TemporaryStorageProvider extends ContentProvider { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.d(Constants.TAG, "Upgrading files db from " + oldVersion + " to " + newVersion); + switch (oldVersion) { + case 1: + db.execSQL("DROP TABLE IF EXISTS files"); + db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" + + COLUMN_ID + " TEXT PRIMARY KEY, " + + COLUMN_NAME + " TEXT, " + + COLUMN_TIME + " INTEGER" + + ");"); + } } } @@ -82,13 +94,13 @@ public class TemporaryStorageProvider extends ContentProvider { private File getFile(Uri uri) throws FileNotFoundException { try { - return getFile(Integer.parseInt(uri.getLastPathSegment())); + return getFile(uri.getLastPathSegment()); } catch (NumberFormatException e) { throw new FileNotFoundException(); } } - private File getFile(int id) { + private File getFile(String id) { return new File(getContext().getCacheDir(), "temp/" + id); } @@ -133,13 +145,15 @@ public class TemporaryStorageProvider extends ContentProvider { if (!values.containsKey(COLUMN_TIME)) { values.put(COLUMN_TIME, System.currentTimeMillis()); } + String uuid = UUID.randomUUID().toString(); + values.put(COLUMN_ID, uuid); int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values); try { - getFile(insert).createNewFile(); + getFile(uuid).createNewFile(); } catch (IOException e) { return null; } - return Uri.withAppendedPath(BASE_URI, Long.toString(insert)); + return Uri.withAppendedPath(BASE_URI, uuid); } @Override @@ -152,7 +166,7 @@ public class TemporaryStorageProvider extends ContentProvider { selectionArgs, null, null, null); if (files != null) { while (files.moveToNext()) { - getFile(files.getInt(0)).delete(); + getFile(files.getString(0)).delete(); } files.close(); return db.getWritableDatabase().delete(TABLE_FILES, selection, selectionArgs); From 51bb96742f95b5b3b1ce68aef76fb5b0ddc5575d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 4 May 2015 14:01:34 +0200 Subject: [PATCH 29/71] Apply patch from https://github.com/open-keychain/open-keychain/issues/1222#issuecomment-98582938 --- .../keychain/pgp/PgpSignEncryptOperation.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 8ecb30cdd..9073e81b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -178,13 +178,20 @@ public class PgpSignEncryptOperation extends BaseOperation { case PIN: case PATTERN: case PASSPHRASE: { - if (cryptoInput.getPassphrase() == null) { + Passphrase localPassphrase = cryptoInput.getPassphrase(); + if (localPassphrase == null) { + try { + localPassphrase = getCachedPassphrase(signingKeyRing.getMasterKeyId(), signingKey.getKeyId()); + } catch (PassphraseCacheInterface.NoSecretKeyException ignored) { + } + } + if (localPassphrase == null) { log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1); return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase( signingKeyRing.getMasterKeyId(), signingKey.getKeyId(), cryptoInput.getSignatureTime())); } - if (!signingKey.unlock(cryptoInput.getPassphrase())) { + if (!signingKey.unlock(localPassphrase)) { log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent); return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } From b726dea244f020660d5260d3211d6500655fe680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 4 May 2015 14:12:42 +0200 Subject: [PATCH 30/71] license header for PgpCertifyOperation --- .../keychain/pgp/PgpCertifyOperation.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java index 90ec3053f..bf2349734 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java @@ -1,3 +1,21 @@ +/* + * 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 + * 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.pgp; @@ -32,7 +50,7 @@ public class PgpCertifyOperation { OperationLog log, int indent, CertifyAction action, - Map signedHashes, + Map signedHashes, Date creationTimestamp) { if (!secretKey.isMasterKey()) { From dedda1603c73b66276931dacf8a7c5d1798db4a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 4 May 2015 14:19:16 +0200 Subject: [PATCH 31/71] Fix dublicated code in CreateKeyEmailFragment --- .../keychain/ui/CreateKeyEmailFragment.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index f338a53a6..552fe6954 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -133,10 +133,6 @@ public class CreateKeyEmailFragment extends Fragment { addEmail(); } }); - - if (mCreateKeyActivity.mAdditionalEmails != null) { - mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); - } } if (mAdditionalEmailModels.isEmpty() && mCreateKeyActivity.mAdditionalEmails != null) { mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); From eb9019c78ce3ec19d5faed1c48734e3eb1bde043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 4 May 2015 14:29:47 +0200 Subject: [PATCH 32/71] Temporary fix for gradle dependency bug --- OpenKeychain/build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 14d82ec46..2a0779125 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -17,6 +17,12 @@ dependencies { androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1' androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.1' + // 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 + configurations.all { + resolutionStrategy.force 'com.android.support:support-annotations:22.1.1' + } + // JCenter etc. compile 'com.eftimoff:android-patternview:1.0.1@aar' compile 'com.journeyapps:zxing-android-embedded:2.3.0@aar' From e1ca612bc43475e8a0d6a289c978d226a9ab78c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 4 May 2015 14:55:56 +0200 Subject: [PATCH 33/71] Use more simple email validation regex --- .../keychain/ui/CreateKeyEmailFragment.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index aeae8a1ad..0a9ddf382 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -26,7 +26,6 @@ import android.support.v4.app.Fragment; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.util.Patterns; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -52,6 +51,10 @@ public class CreateKeyEmailFragment extends Fragment { private ArrayList mAdditionalEmailModels; private EmailAdapter mEmailAdapter; + // NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS + // EMAIL_ADDRESS fails for mails with umlauts for example + private static final Pattern EMAIL_PATTERN = Pattern.compile(".[\\S]+@.[\\S]+\\.[a-z]+"); + /** * Creates new instance of this fragment */ @@ -146,7 +149,7 @@ public class CreateKeyEmailFragment extends Fragment { * @return */ private boolean checkEmail(String email, boolean additionalEmail) { - //check for email format or if the user did any input + // check for email format or if the user did any input if (!isEmailFormatValid(email)) { Notify.create(getActivity(), getString(R.string.create_key_email_invalid_email), @@ -154,7 +157,7 @@ public class CreateKeyEmailFragment extends Fragment { return false; } - //check for duplicated emails + // check for duplicated emails if (!additionalEmail && isEmailDuplicatedInsideAdapter(email) || additionalEmail && mEmailEdit.getText().length() > 0 && email.equals(mEmailEdit.getText().toString())) { Notify.create(getActivity(), @@ -174,10 +177,8 @@ public class CreateKeyEmailFragment extends Fragment { * @return */ private boolean isEmailFormatValid(String email) { - Pattern emailPattern = Patterns.EMAIL_ADDRESS; - - //check for email format or if the user did any input - return !(email.length() == 0 || !emailPattern.matcher(email).matches()); + // check for email format or if the user did any input + return !(email.length() == 0 || !EMAIL_PATTERN.matcher(email).matches()); } /** From 2cd1cf9192aee76a0f2481281148783519d208a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 4 May 2015 15:27:19 +0200 Subject: [PATCH 34/71] Fix email pattern --- .../sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 0a9ddf382..64dc71785 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -53,7 +53,7 @@ public class CreateKeyEmailFragment extends Fragment { // NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS // EMAIL_ADDRESS fails for mails with umlauts for example - private static final Pattern EMAIL_PATTERN = Pattern.compile(".[\\S]+@.[\\S]+\\.[a-z]+"); + private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$"); /** * Creates new instance of this fragment From f3090f2939ede5db92afbd5fd52e9a509556a15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 4 May 2015 15:34:48 +0200 Subject: [PATCH 35/71] Display notify above keyboard in CreateKeyEmailFragment --- .../keychain/ui/CreateKeyEmailFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 64dc71785..dbff4fb9f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -153,7 +153,7 @@ public class CreateKeyEmailFragment extends Fragment { if (!isEmailFormatValid(email)) { Notify.create(getActivity(), getString(R.string.create_key_email_invalid_email), - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this); return false; } @@ -162,7 +162,7 @@ public class CreateKeyEmailFragment extends Fragment { mEmailEdit.getText().length() > 0 && email.equals(mEmailEdit.getText().toString())) { Notify.create(getActivity(), getString(R.string.create_key_email_already_exists_text), - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this); return false; } From 2583f77f07708b388043991c0dd6c9ef1d14f523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 4 May 2015 20:47:19 +0200 Subject: [PATCH 36/71] Fix cancel/skip in create key wizard --- .../keychain/ui/CreateKeyActivity.java | 2 +- .../keychain/ui/CreateKeyStartFragment.java | 83 ++++++++----------- ...java => CreateKeyYubiKeyWaitFragment.java} | 2 +- 3 files changed, 35 insertions(+), 52 deletions(-) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/{CreateKeyYubiWaitFragment.java => CreateKeyYubiKeyWaitFragment.java} (96%) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index 0b203614b..dfb94ebb9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -99,7 +99,7 @@ public class CreateKeyActivity extends BaseNfcActivity { if (mFirstTime) { setTitle(R.string.app_name); - setActionBarIcon(R.drawable.ic_launcher); + mToolbar.setNavigationIcon(null); mToolbar.setNavigationOnClickListener(null); } else { setTitle(R.string.title_manage_my_keys); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java index 3f56949f5..1a844e6e4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Dominik Schürmann + * Copyright (C) 2014-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 @@ -18,37 +18,20 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.Fragment; -import android.support.v7.widget.DefaultItemAnimator; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageButton; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -import org.sufficientlysecure.keychain.ui.dialog.AddEmailDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; -import org.sufficientlysecure.keychain.ui.widget.EmailEditText; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; -import java.util.ArrayList; -import java.util.List; - public class CreateKeyStartFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; @@ -56,8 +39,8 @@ public class CreateKeyStartFragment extends Fragment { View mCreateKey; View mImportKey; View mYubiKey; - TextView mCancel; - public static final int REQUEST_CODE_CREATE_OR_IMPORT_KEY = 0x00007012; + TextView mSkipOrCancel; + public static final int REQUEST_CODE_IMPORT_KEY = 0x00007012; /** * Creates new instance of this fragment @@ -79,12 +62,12 @@ public class CreateKeyStartFragment extends Fragment { mCreateKey = view.findViewById(R.id.create_key_create_key_button); mImportKey = view.findViewById(R.id.create_key_import_button); mYubiKey = view.findViewById(R.id.create_key_yubikey_button); - mCancel = (TextView) view.findViewById(R.id.create_key_cancel); + mSkipOrCancel = (TextView) view.findViewById(R.id.create_key_cancel); if (mCreateKeyActivity.mFirstTime) { - mCancel.setText(R.string.first_time_skip); + mSkipOrCancel.setText(R.string.first_time_skip); } else { - mCancel.setText(R.string.btn_do_not_save); + mSkipOrCancel.setText(R.string.btn_do_not_save); } mCreateKey.setOnClickListener(new View.OnClickListener() { @@ -98,7 +81,7 @@ public class CreateKeyStartFragment extends Fragment { mYubiKey.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - CreateKeyYubiWaitFragment frag = new CreateKeyYubiWaitFragment(); + CreateKeyYubiKeyWaitFragment frag = new CreateKeyYubiKeyWaitFragment(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } }); @@ -108,48 +91,48 @@ public class CreateKeyStartFragment extends Fragment { public void onClick(View v) { Intent intent = new Intent(mCreateKeyActivity, ImportKeysActivity.class); intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); - startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY); + startActivityForResult(intent, REQUEST_CODE_IMPORT_KEY); } }); - mCancel.setOnClickListener(new View.OnClickListener() { + mSkipOrCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - finishSetup(null); + if (mCreateKeyActivity.mFirstTime) { + Preferences prefs = Preferences.getPreferences(mCreateKeyActivity); + prefs.setFirstTime(false); + Intent intent = new Intent(mCreateKeyActivity, MainActivity.class); + startActivity(intent); + mCreateKeyActivity.finish(); + } else { + // just finish activity and return data + mCreateKeyActivity.setResult(Activity.RESULT_CANCELED); + mCreateKeyActivity.finish(); + } } }); return view; } - - private void finishSetup(Intent srcData) { - if (mCreateKeyActivity.mFirstTime) { - Preferences prefs = Preferences.getPreferences(mCreateKeyActivity); - prefs.setFirstTime(false); - } - Intent intent = new Intent(mCreateKeyActivity, MainActivity.class); - // give intent through to display notify - if (srcData != null) { - intent.putExtras(srcData); - } - startActivity(intent); - mCreateKeyActivity.finish(); - } - - // workaround for https://code.google.com/p/android/issues/detail?id=61394 -// @Override -// public boolean onKeyDown(int keyCode, KeyEvent event) { -// return keyCode == KeyEvent.KEYCODE_MENU || super.onKeyDown(keyCode, event); -// } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_CODE_CREATE_OR_IMPORT_KEY) { + if (requestCode == REQUEST_CODE_IMPORT_KEY) { if (resultCode == Activity.RESULT_OK) { - finishSetup(data); + if (mCreateKeyActivity.mFirstTime) { + Preferences prefs = Preferences.getPreferences(mCreateKeyActivity); + prefs.setFirstTime(false); + Intent intent = new Intent(mCreateKeyActivity, MainActivity.class); + intent.putExtras(data); + startActivity(intent); + mCreateKeyActivity.finish(); + } else { + // just finish activity and return data + mCreateKeyActivity.setResult(Activity.RESULT_OK, data); + mCreateKeyActivity.finish(); + } } } else { Log.e(Constants.TAG, "No valid request code!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java similarity index 96% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java index 579dddf79..0b8586c0a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java @@ -28,7 +28,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -public class CreateKeyYubiWaitFragment extends Fragment { +public class CreateKeyYubiKeyWaitFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; View mBackButton; From 291f95db5ad2028ef252448e47b4ad22a297bf33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 4 May 2015 21:13:07 +0200 Subject: [PATCH 37/71] Fix revoked/expired state in decrypt activity --- .../keychain/ui/DecryptFilesFragment.java | 2 +- .../keychain/ui/DecryptFragment.java | 26 +++++----------- .../keychain/ui/DecryptTextFragment.java | 30 +++++++++---------- .../main/res/layout/decrypt_text_fragment.xml | 6 ++-- 4 files changed, 27 insertions(+), 37 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java index 6c1902af1..e5ddcbbd6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java @@ -309,7 +309,7 @@ public class DecryptFilesFragment extends DecryptFragment { } @Override - protected void onVerifyLoaded(boolean verified) { + protected void onVerifyLoaded(boolean hideErrorOverlay) { } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 9c51893ce..230f478ae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -55,7 +55,6 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.Preferences; - public abstract class DecryptFragment extends CryptoOperationFragment implements LoaderManager.LoaderCallbacks { @@ -91,7 +90,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements mSignatureName = (TextView) getActivity().findViewById(R.id.result_signature_name); mSignatureEmail = (TextView) getActivity().findViewById(R.id.result_signature_email); mSignatureAction = (TextView) getActivity().findViewById(R.id.result_signature_action); - } private void lookupUnknownKey(long unknownKeyId) { @@ -113,12 +111,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements final ImportKeyResult result = returnData.getParcelable(OperationResult.EXTRA_RESULT); - // if (!result.success()) { - result.createNotify(getActivity()).show(); - // } + result.createNotify(getActivity()).show(); getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, DecryptFragment.this); - } } }; @@ -153,7 +148,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); getActivity().startService(intent); - } private void showKey(long keyId) { @@ -205,7 +199,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements } getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, this); - } private void setSignatureLayoutVisibility(int visibility) { @@ -228,8 +221,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.HAS_ANY_SECRET, }; @@ -237,10 +228,8 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements @SuppressWarnings("unused") static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_USER_ID = 2; - static final int INDEX_IS_REVOKED = 3; - static final int INDEX_IS_EXPIRED = 4; - static final int INDEX_VERIFIED = 5; - static final int INDEX_HAS_ANY_SECRET = 6; + static final int INDEX_VERIFIED = 3; + static final int INDEX_HAS_ANY_SECRET = 4; @Override public Loader onCreateLoader(int id, Bundle args) { @@ -282,8 +271,10 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements getActivity(), mSignatureResult.getKeyId())); } - boolean isRevoked = data.getInt(INDEX_IS_REVOKED) != 0; - boolean isExpired = data.getInt(INDEX_IS_EXPIRED) != 0; + // NOTE: Don't use revoked and expired fields from database, they don't show + // revoked/expired subkeys + boolean isRevoked = mSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED; + boolean isExpired = mSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED; boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; boolean isYours = data.getInt(INDEX_HAS_ANY_SECRET) != 0; @@ -344,7 +335,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements } setSignatureLayoutVisibility(View.GONE); - } private void showUnknownKeyStatus() { @@ -407,6 +397,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements } - protected abstract void onVerifyLoaded(boolean verified); + protected abstract void onVerifyLoaded(boolean hideErrorOverlay); } 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 6f576a112..b8f1aee63 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java @@ -50,8 +50,8 @@ public class DecryptTextFragment extends DecryptFragment { public static final String ARG_CIPHERTEXT = "ciphertext"; // view - private LinearLayout mValidLayout; - private LinearLayout mInvalidLayout; + private LinearLayout mContentLayout; + private LinearLayout mErrorOverlayLayout; private TextView mText; // model @@ -78,16 +78,16 @@ public class DecryptTextFragment extends DecryptFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false); - mValidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_valid); - mInvalidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_invalid); + mContentLayout = (LinearLayout) view.findViewById(R.id.decrypt_content); + mErrorOverlayLayout = (LinearLayout) view.findViewById(R.id.decrypt_error_overlay); mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext); - Button vInvalidButton = (Button) view.findViewById(R.id.decrypt_text_invalid_button); - vInvalidButton.setOnClickListener(new View.OnClickListener() { + Button vErrorOverlayButton = (Button) view.findViewById(R.id.decrypt_error_overlay_button); + vErrorOverlayButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mInvalidLayout.setVisibility(View.GONE); - mValidLayout.setVisibility(View.VISIBLE); + mErrorOverlayLayout.setVisibility(View.GONE); + mContentLayout.setVisibility(View.VISIBLE); } }); @@ -244,17 +244,17 @@ public class DecryptTextFragment extends DecryptFragment { } @Override - protected void onVerifyLoaded(boolean verified) { + protected void onVerifyLoaded(boolean hideErrorOverlay) { - mShowMenuOptions = verified; + mShowMenuOptions = hideErrorOverlay; getActivity().supportInvalidateOptionsMenu(); - if (verified) { - mInvalidLayout.setVisibility(View.GONE); - mValidLayout.setVisibility(View.VISIBLE); + if (hideErrorOverlay) { + mErrorOverlayLayout.setVisibility(View.GONE); + mContentLayout.setVisibility(View.VISIBLE); } else { - mInvalidLayout.setVisibility(View.VISIBLE); - mValidLayout.setVisibility(View.GONE); + mErrorOverlayLayout.setVisibility(View.VISIBLE); + mContentLayout.setVisibility(View.GONE); } } diff --git a/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml b/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml index 5b74905e7..ea6be462f 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml @@ -6,7 +6,7 @@ @@ -36,7 +36,7 @@