aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java126
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java132
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java247
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java61
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java83
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java106
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java41
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java92
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java39
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java70
13 files changed, 970 insertions, 51 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java
new file mode 100644
index 000000000..43ccac24f
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.util;
+
+import android.content.Context;
+
+import org.spongycastle.util.Arrays;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.BitSet;
+
+public class ExperimentalWordConfirm {
+
+ public static String getWords(Context context, byte[] fingerprintBlob) {
+ ArrayList<String> words = new ArrayList<>();
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(
+ context.getAssets().open("word_confirm_list.txt"),
+ "UTF-8"
+ ));
+
+ String line = reader.readLine();
+ while (line != null) {
+ words.add(line);
+
+ line = reader.readLine();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("IOException", e);
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ String fingerprint = "";
+
+ // NOTE: 160 bit SHA-1 truncated to 156 bit
+ byte[] fingerprintBlobTruncated = Arrays.copyOfRange(fingerprintBlob, 0, 156 / 8);
+
+ // TODO: implement key stretching to minimize fp length?
+
+ // BitSet bits = BitSet.valueOf(fingerprintBlob); // min API 19 and little endian!
+ BitSet bits = bitSetToByteArray(fingerprintBlobTruncated);
+ Log.d(Constants.TAG, "bits: " + bits.toString());
+
+ final int CHUNK_SIZE = 13;
+ final int LAST_CHUNK_INDEX = fingerprintBlobTruncated.length * 8 / CHUNK_SIZE; // 12
+ Log.d(Constants.TAG, "LAST_CHUNK_INDEX: " + LAST_CHUNK_INDEX);
+
+ int from = 0;
+ int to = CHUNK_SIZE;
+ for (int i = 0; i < (LAST_CHUNK_INDEX + 1); i++) {
+ Log.d(Constants.TAG, "from: " + from + " to: " + to);
+
+ BitSet setIndex = bits.get(from, to);
+ int wordIndex = (int) bitSetToLong(setIndex);
+ // int wordIndex = (int) setIndex.toLongArray()[0]; // min API 19
+
+ fingerprint += words.get(wordIndex);
+
+ if (i != LAST_CHUNK_INDEX) {
+ // line break every 3 words
+ if (to % (CHUNK_SIZE * 3) == 0) {
+ fingerprint += "\n";
+ } else {
+ fingerprint += " ";
+ }
+ }
+
+ from = to;
+ to += CHUNK_SIZE;
+ }
+
+ return fingerprint;
+ }
+
+ /**
+ * Returns a BitSet containing the values in bytes.
+ * BIG ENDIAN!
+ */
+ private static BitSet bitSetToByteArray(byte[] bytes) {
+ int arrayLength = bytes.length * 8;
+ BitSet bits = new BitSet();
+
+ for (int i = 0; i < arrayLength; i++) {
+ if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
+ bits.set(i);
+ }
+ }
+ return bits;
+ }
+
+ private static long bitSetToLong(BitSet bits) {
+ long value = 0L;
+ for (int i = 0; i < bits.length(); ++i) {
+ value += bits.get(i) ? (1L << i) : 0L;
+ }
+ return value;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java
index eb5c3df45..902a7ec56 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java
@@ -18,9 +18,11 @@
package org.sufficientlysecure.keychain.ui.util;
import android.content.Context;
+import android.content.res.Resources.Theme;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.StrikethroughSpan;
+import android.util.TypedValue;
public class FormattingUtils {
@@ -32,4 +34,10 @@ public class FormattingUtils {
return (int) ((px / context.getResources().getDisplayMetrics().density) + 0.5f);
}
+ public static int getColorFromAttr(Context context, int attr) {
+ TypedValue typedValue = new TypedValue();
+ Theme theme = context.getTheme();
+ theme.resolveAttribute(attr, typedValue, true);
+ return typedValue.data;
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java
index 69338aa3e..ac34d5526 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java
@@ -22,6 +22,7 @@ import android.text.Spannable;
import android.text.style.ForegroundColorSpan;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -44,9 +45,12 @@ public class Highlighter {
Pattern pattern = Pattern.compile("(?i)(" + mQuery.trim().replaceAll("\\s+", "|") + ")");
Matcher matcher = pattern.matcher(text);
+
+ int colorEmphasis = FormattingUtils.getColorFromAttr(mContext, R.attr.colorEmphasis);
+
while (matcher.find()) {
highlight.setSpan(
- new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)),
+ new ForegroundColorSpan(colorEmphasis),
matcher.start(),
matcher.end(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java
new file mode 100644
index 000000000..b2213ed10
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.util;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.v7.app.AlertDialog;
+import android.view.ContextThemeWrapper;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class InstallDialogFragmentHelper {
+ private static final String ARG_MESSENGER = "messenger";
+ private static final String ARG_TITLE = "title";
+ private static final String ARG_MESSAGE = "message";
+ private static final String ARG_MIDDLE_BUTTON = "middleButton";
+ private static final String ARG_INSTALL_PATH = "installPath";
+ private static final String ARG_USE_MIDDLE_BUTTON = "useMiddleButton";
+
+ private static final String PLAY_STORE_PATH = "market://search?q=pname:";
+
+ public static void wrapIntoArgs(Messenger messenger, int title, int message, String packageToInstall,
+ int middleButton, boolean useMiddleButton, Bundle args) {
+ args.putParcelable(ARG_MESSENGER, messenger);
+
+ args.putInt(ARG_TITLE, title);
+ args.putInt(ARG_MESSAGE, message);
+ args.putInt(ARG_MIDDLE_BUTTON, middleButton);
+ args.putString(ARG_INSTALL_PATH, PLAY_STORE_PATH + packageToInstall);
+ args.putBoolean(ARG_USE_MIDDLE_BUTTON, useMiddleButton);
+ }
+
+ public static AlertDialog getInstallDialogFromArgs(Bundle args, final Activity activity,
+ final int messengerMiddleButtonClicked,
+ final int messengerDialogDimissed) {
+ final Messenger messenger = args.getParcelable(ARG_MESSENGER);
+
+ final int title = args.getInt(ARG_TITLE);
+ final int message = args.getInt(ARG_MESSAGE);
+ final int middleButton = args.getInt(ARG_MIDDLE_BUTTON);
+ final String installPath = args.getString(ARG_INSTALL_PATH);
+ final boolean useMiddleButton = args.getBoolean(ARG_USE_MIDDLE_BUTTON);
+
+ ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity);
+ CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme);
+
+ builder.setTitle(title).setMessage(message);
+
+ builder.setNegativeButton(R.string.orbot_install_dialog_cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Message msg = Message.obtain();
+ msg.what = messengerDialogDimissed;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
+ } catch (NullPointerException e) {
+ Log.w(Constants.TAG, "Messenger is null!", e);
+ }
+ }
+ });
+
+ builder.setPositiveButton(R.string.orbot_install_dialog_install,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Uri uri = Uri.parse(installPath);
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ activity.startActivity(intent);
+
+ Message msg = Message.obtain();
+ msg.what = messengerDialogDimissed;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
+ } catch (NullPointerException e) {
+ Log.w(Constants.TAG, "Messenger is null!", e);
+ }
+ }
+ }
+ );
+
+ if (useMiddleButton) {
+ builder.setNeutralButton(middleButton,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Message msg = Message.obtain();
+ msg.what = messengerMiddleButtonClicked;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
+ } catch (NullPointerException e) {
+ Log.w(Constants.TAG, "Messenger is null!", e);
+ }
+ }
+ }
+ );
+ }
+
+ return builder.show();
+ }
+}
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 3d98034d2..284c17e7a 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
@@ -24,9 +24,12 @@ import android.graphics.PorterDuff;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
+import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import org.openintents.openpgp.OpenPgpDecryptionResult;
+import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.asn1.ASN1ObjectIdentifier;
import org.spongycastle.asn1.nist.NISTNamedCurves;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
@@ -34,6 +37,8 @@ import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
+import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.util.Log;
@@ -218,14 +223,11 @@ public class KeyFormattingUtils {
public static String convertFingerprintToHex(byte[] fingerprint) {
// NOTE: Even though v3 keys are not imported we need to support both fingerprints for
// display/comparison before import
- // Also better cut of unneeded parts, e.g., for fingerprints returned from YubiKeys
- if (fingerprint.length < 20) {
- // v3 key fingerprint with 128 bit (MD5)
- return Hex.toHexString(fingerprint, 0, 16).toLowerCase(Locale.ENGLISH);
- } else {
- // v4 key fingerprint with 160 bit (SHA1)
- return Hex.toHexString(fingerprint, 0, 20).toLowerCase(Locale.ENGLISH);
+ if (fingerprint.length != 16 && fingerprint.length != 20) {
+ throw new IllegalArgumentException("No valid v3 or v4 fingerprint!");
}
+
+ return Hex.toHexString(fingerprint).toLowerCase(Locale.ENGLISH);
}
public static long getKeyIdFromFingerprint(byte[] fingerprint) {
@@ -383,7 +385,6 @@ public class KeyFormattingUtils {
/**
* Converts the given bytes to a unique RGB color using SHA1 algorithm
*
- * @param bytes
* @return an integer array containing 3 numeric color representations (Red, Green, Black)
* @throws java.security.NoSuchAlgorithmException
* @throws java.security.DigestException
@@ -401,7 +402,7 @@ public class KeyFormattingUtils {
public static final int DEFAULT_COLOR = -1;
- public static enum State {
+ public enum State {
REVOKED,
EXPIRED,
VERIFIED,
@@ -411,7 +412,8 @@ public class KeyFormattingUtils {
UNVERIFIED,
UNKNOWN_KEY,
INVALID,
- NOT_SIGNED
+ NOT_SIGNED,
+ INSECURE
}
public static void setStatusImage(Context context, ImageView statusIcon, State state) {
@@ -427,9 +429,196 @@ public class KeyFormattingUtils {
setStatusImage(context, statusIcon, statusText, state, color, false);
}
+ public interface StatusHolder {
+ ImageView getEncryptionStatusIcon();
+ TextView getEncryptionStatusText();
+
+ ImageView getSignatureStatusIcon();
+ TextView getSignatureStatusText();
+
+ View getSignatureLayout();
+ TextView getSignatureUserName();
+ TextView getSignatureUserEmail();
+ TextView getSignatureAction();
+
+ boolean hasEncrypt();
+
+ }
+
+ @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated
+ public static void setStatus(Context context, StatusHolder holder, DecryptVerifyResult result) {
+
+ if (holder.hasEncrypt()) {
+ OpenPgpDecryptionResult decryptionResult = result.getDecryptionResult();
+
+ int encText, encIcon, encColor;
+
+ switch (decryptionResult.getResult()) {
+ case OpenPgpDecryptionResult.RESULT_ENCRYPTED: {
+ encText = R.string.decrypt_result_encrypted;
+ encIcon = R.drawable.status_lock_closed_24dp;
+ encColor = R.color.key_flag_green;
+ break;
+ }
+
+ case OpenPgpDecryptionResult.RESULT_INSECURE: {
+ encText = R.string.decrypt_result_insecure;
+ encIcon = R.drawable.status_signature_invalid_cutout_24dp;
+ encColor = R.color.key_flag_red;
+ break;
+ }
+
+ default:
+ case OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED: {
+ encText = R.string.decrypt_result_not_encrypted;
+ encIcon = R.drawable.status_lock_open_24dp;
+ encColor = R.color.key_flag_red;
+ break;
+ }
+ }
+
+ int encColorRes = context.getResources().getColor(encColor);
+ holder.getEncryptionStatusIcon().setColorFilter(encColorRes, PorterDuff.Mode.SRC_IN);
+ holder.getEncryptionStatusIcon().setImageDrawable(context.getResources().getDrawable(encIcon));
+ holder.getEncryptionStatusText().setText(encText);
+ holder.getEncryptionStatusText().setTextColor(encColorRes);
+ }
+
+ OpenPgpSignatureResult signatureResult = result.getSignatureResult();
+
+ int sigText, sigIcon, sigColor;
+ int sigActionText, sigActionIcon;
+
+ switch (signatureResult.getResult()) {
+
+ case OpenPgpSignatureResult.RESULT_NO_SIGNATURE: {
+ // no signature
+
+ sigText = R.string.decrypt_result_no_signature;
+ sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
+ sigColor = R.color.key_flag_gray;
+
+ // won't be used, but makes compiler happy
+ sigActionText = 0;
+ sigActionIcon = 0;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_VALID_CONFIRMED: {
+ sigText = R.string.decrypt_result_signature_certified;
+ sigIcon = R.drawable.status_signature_verified_cutout_24dp;
+ sigColor = R.color.key_flag_green;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_VALID_UNCONFIRMED: {
+ sigText = R.string.decrypt_result_signature_uncertified;
+ sigIcon = R.drawable.status_signature_unverified_cutout_24dp;
+ sigColor = R.color.key_flag_orange;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_INVALID_KEY_REVOKED: {
+ sigText = R.string.decrypt_result_signature_revoked_key;
+ sigIcon = R.drawable.status_signature_revoked_cutout_24dp;
+ sigColor = R.color.key_flag_red;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED: {
+ sigText = R.string.decrypt_result_signature_expired_key;
+ sigIcon = R.drawable.status_signature_expired_cutout_24dp;
+ sigColor = R.color.key_flag_red;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_KEY_MISSING: {
+ sigText = R.string.decrypt_result_signature_missing_key;
+ sigIcon = R.drawable.status_signature_unknown_cutout_24dp;
+ sigColor = R.color.key_flag_red;
+
+ sigActionText = R.string.decrypt_result_action_Lookup;
+ sigActionIcon = R.drawable.ic_file_download_grey_24dp;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_INVALID_INSECURE: {
+ sigText = R.string.decrypt_result_insecure_cryptography;
+ sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
+ sigColor = R.color.key_flag_red;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ default:
+ case OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE: {
+ sigText = R.string.decrypt_result_invalid_signature;
+ sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
+ sigColor = R.color.key_flag_red;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ }
+
+ int sigColorRes = context.getResources().getColor(sigColor);
+ holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN);
+ holder.getSignatureStatusIcon().setImageDrawable(context.getResources().getDrawable(sigIcon));
+ holder.getSignatureStatusText().setText(sigText);
+ holder.getSignatureStatusText().setTextColor(sigColorRes);
+
+ if (signatureResult.getResult() != OpenPgpSignatureResult.RESULT_NO_SIGNATURE) {
+ // has a signature, thus display layouts
+
+ holder.getSignatureLayout().setVisibility(View.VISIBLE);
+
+ holder.getSignatureAction().setText(sigActionText);
+ holder.getSignatureAction().setCompoundDrawablesWithIntrinsicBounds(
+ 0, 0, sigActionIcon, 0);
+
+ String userId = result.getSignatureResult().getPrimaryUserId();
+ KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
+ if (userIdSplit.name != null) {
+ holder.getSignatureUserName().setText(userIdSplit.name);
+ } else {
+ holder.getSignatureUserName().setText(R.string.user_id_no_name);
+ }
+ if (userIdSplit.email != null) {
+ holder.getSignatureUserEmail().setVisibility(View.VISIBLE);
+ holder.getSignatureUserEmail().setText(userIdSplit.email);
+ } else {
+ holder.getSignatureUserEmail().setVisibility(View.GONE);
+ }
+
+ } else {
+
+ holder.getSignatureLayout().setVisibility(View.GONE);
+
+ }
+
+
+ }
+
/**
* Sets status image based on constant
*/
+ @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21
public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText,
State state, int color, boolean big) {
switch (state) {
@@ -443,7 +632,7 @@ public class KeyFormattingUtils {
context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_24dp));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_green_light;
+ color = R.color.key_flag_green;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -456,7 +645,7 @@ public class KeyFormattingUtils {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_lock_closed_24dp));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_green_light;
+ color = R.color.key_flag_green;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -475,7 +664,7 @@ public class KeyFormattingUtils {
context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_24dp));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_orange_light;
+ color = R.color.key_flag_orange;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -488,7 +677,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_red_light;
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -507,7 +696,7 @@ public class KeyFormattingUtils {
context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_24dp));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_red_light;
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -525,7 +714,25 @@ public class KeyFormattingUtils {
context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_24dp));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_red_light;
+ color = R.color.key_flag_red;
+ }
+ statusIcon.setColorFilter(context.getResources().getColor(color),
+ PorterDuff.Mode.SRC_IN);
+ if (statusText != null) {
+ statusText.setTextColor(context.getResources().getColor(color));
+ }
+ break;
+ }
+ case INSECURE: {
+ if (big) {
+ statusIcon.setImageDrawable(
+ context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_96dp));
+ } else {
+ statusIcon.setImageDrawable(
+ context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp));
+ }
+ if (color == KeyFormattingUtils.DEFAULT_COLOR) {
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -538,7 +745,7 @@ public class KeyFormattingUtils {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_lock_open_24dp));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_red_light;
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -551,7 +758,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_red_light;
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -564,7 +771,7 @@ public class KeyFormattingUtils {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_red_light;
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -578,7 +785,7 @@ public class KeyFormattingUtils {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.bg_gray;
+ color = R.color.key_flag_gray;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
index 7e07ed818..7dfd56430 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
@@ -38,33 +38,25 @@ import org.sufficientlysecure.keychain.util.FabContainer;
public class Notify {
public static enum Style {
- OK, WARN, ERROR;
+ OK (R.color.android_green_light), WARN(R.color.android_orange_light), ERROR(R.color.android_red_light);
- public void applyToBar(Snackbar bar) {
+ public final int mLineColor;
- switch (this) {
- case OK:
- // bar.actionColorResource(R.color.android_green_light);
- bar.lineColorResource(R.color.android_green_light);
- break;
- case WARN:
- // bar.textColorResource(R.color.android_orange_light);
- bar.lineColorResource(R.color.android_orange_light);
- break;
- case ERROR:
- // bar.textColorResource(R.color.android_red_light);
- bar.lineColorResource(R.color.android_red_light);
- break;
- }
+ Style(int color) {
+ mLineColor = color;
+ }
+ public void applyToBar(Snackbar bar) {
+ bar.lineColorResource(mLineColor);
}
}
public static final int LENGTH_INDEFINITE = 0;
public static final int LENGTH_LONG = 3500;
+ public static final int LENGTH_SHORT = 1500;
public static Showable create(final Activity activity, String text, int duration, Style style,
- final ActionListener actionListener, int actionResId) {
+ final ActionListener actionListener, Integer actionResId) {
final Snackbar snackbar = Snackbar.with(activity)
.type(SnackbarType.MULTI_LINE)
.text(text);
@@ -77,14 +69,16 @@ public class Notify {
style.applyToBar(snackbar);
+ if (actionResId != null) {
+ snackbar.actionLabel(actionResId);
+ }
if (actionListener != null) {
- snackbar.actionLabel(actionResId)
- .actionListener(new ActionClickListener() {
- @Override
- public void onActionClicked(Snackbar snackbar) {
- actionListener.onAction();
- }
- });
+ snackbar.actionListener(new ActionClickListener() {
+ @Override
+ public void onActionClicked(Snackbar snackbar) {
+ actionListener.onAction();
+ }
+ });
}
if (activity instanceof FabContainer) {
@@ -108,6 +102,13 @@ public class Notify {
}
@Override
+ public void show(Fragment fragment, boolean animate) {
+ snackbar.animation(animate);
+ snackbar.dismissOnActionClicked(animate);
+ show(fragment);
+ }
+
+ @Override
public void show(Fragment fragment) {
if (fragment != null) {
View view = fragment.getView();
@@ -134,7 +135,7 @@ public class Notify {
}
public static Showable create(Activity activity, String text, int duration, Style style) {
- return create(activity, text, duration, style, null, -1);
+ return create(activity, text, duration, style, null, null);
}
public static Showable create(Activity activity, String text, Style style) {
@@ -159,24 +160,26 @@ public class Notify {
/**
* Shows the notification on the bottom of the Activity.
*/
- public void show();
+ void show();
+
+ void show(Fragment fragment, boolean animate);
/**
* Shows the notification on the bottom of the Fragment.
*/
- public void show(Fragment fragment);
+ void show(Fragment fragment);
/**
* Shows the notification on the given ViewGroup.
* The viewGroup should be either a RelativeLayout or FrameLayout.
*/
- public void show(ViewGroup viewGroup);
+ void show(ViewGroup viewGroup);
}
public interface ActionListener {
- public void onAction();
+ void onAction();
}
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 5f71abdab..a6394a3fb 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
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.util;
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.net.Uri;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
@@ -33,17 +34,24 @@ import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Hashtable;
+import java.util.Locale;
/**
* Copied from Bitcoin Wallet
*/
public class QrCodeUtils {
+ public static Bitmap getQRCodeBitmap(final Uri uri, final int size) {
+ // for URIs we want alphanumeric encoding to save space, thus make everything upper case!
+ // zxing will then select Mode.ALPHANUMERIC internally
+ return getQRCodeBitmap(uri.toString().toUpperCase(Locale.ENGLISH), size);
+ }
+
/**
* Generate Bitmap with QR Code based on input.
* @return QR Code as Bitmap
*/
- public static Bitmap getQRCodeBitmap(final String input, final int size) {
+ private static Bitmap getQRCodeBitmap(final String input, final int size) {
try {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java
new file mode 100644
index 000000000..375483d89
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 Thialfihar <thi@thialfihar.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.ContextThemeWrapper;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Preferences;
+
+public class ThemeChanger {
+ private Activity mContext;
+ private Preferences mPreferences;
+ private String mCurrentTheme = null;
+
+ private int mLightResId;
+ private int mDarkResId;
+
+ static public ContextThemeWrapper getDialogThemeWrapper(Context context) {
+ Preferences preferences = Preferences.getPreferences(context);
+
+ // if the dialog is displayed from the application class, design is missing.
+ // hack to get holo design (which is not automatically applied due to activity's
+ // Theme.NoDisplay)
+ if (Constants.Pref.Theme.DARK.equals(preferences.getTheme())) {
+ return new ContextThemeWrapper(context, R.style.Theme_Keychain_Dark);
+ } else {
+ return new ContextThemeWrapper(context, R.style.Theme_Keychain_Light);
+ }
+ }
+
+ public void setThemes(int lightResId, int darkResId) {
+ mLightResId = lightResId;
+ mDarkResId = darkResId;
+ }
+
+ public ThemeChanger(Activity context) {
+ mContext = context;
+ mPreferences = Preferences.getPreferences(mContext);
+ }
+
+ /**
+ * Apply the theme set in preferences if it isn't equal to mCurrentTheme
+ * anymore or mCurrentTheme hasn't been set yet.
+ * If a new theme is applied in this method, then return true, so
+ * the caller can re-create the activity, if need be.
+ */
+ public boolean changeTheme() {
+ String newTheme = mPreferences.getTheme();
+ if (mCurrentTheme != null && mCurrentTheme.equals(newTheme)) {
+ return false;
+ }
+
+ int themeId = mLightResId;
+ if (Constants.Pref.Theme.DARK.equals(newTheme)) {
+ themeId = mDarkResId;
+ }
+
+ ContextThemeWrapper w = new ContextThemeWrapper(mContext, themeId);
+ mContext.getTheme().setTo(w.getTheme());
+ mCurrentTheme = newTheme;
+
+ return true;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java
new file mode 100644
index 000000000..95199bcd5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui.util.recyclerview;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+public class DividerItemDecoration extends RecyclerView.ItemDecoration {
+
+ private static final int[] ATTRS = new int[]{
+ android.R.attr.listDivider
+ };
+
+ public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
+
+ public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
+
+ private Drawable mDivider;
+
+ private int mOrientation;
+
+ public DividerItemDecoration(Context context, int orientation) {
+ final TypedArray a = context.obtainStyledAttributes(ATTRS);
+ mDivider = a.getDrawable(0);
+ a.recycle();
+ setOrientation(orientation);
+ }
+
+ public void setOrientation(int orientation) {
+ if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
+ throw new IllegalArgumentException("invalid orientation");
+ }
+ mOrientation = orientation;
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ if (mOrientation == VERTICAL_LIST) {
+ drawVertical(c, parent);
+ } else {
+ drawHorizontal(c, parent);
+ }
+ }
+
+ public void drawVertical(Canvas c, RecyclerView parent) {
+ final int left = parent.getPaddingLeft();
+ final int right = parent.getWidth() - parent.getPaddingRight();
+
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ final int top = child.getBottom() + params.bottomMargin;
+ final int bottom = top + mDivider.getIntrinsicHeight();
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+
+ public void drawHorizontal(Canvas c, RecyclerView parent) {
+ final int top = parent.getPaddingTop();
+ final int bottom = parent.getHeight() - parent.getPaddingBottom();
+
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ final int left = child.getRight() + params.rightMargin;
+ final int right = left + mDivider.getIntrinsicHeight();
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ if (mOrientation == VERTICAL_LIST) {
+ outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
+ } else {
+ outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
+ }
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java
new file mode 100644
index 000000000..c691182bf
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 Paul Burke
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui.util.recyclerview;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+
+/**
+ * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}.
+ */
+public interface ItemTouchHelperAdapter {
+
+ /**
+ * Called when an item has been dragged far enough to trigger a move. This is called every time
+ * an item is shifted, and <strong>not</strong> at the end of a "drop" event.<br/>
+ * <br/>
+ * Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after
+ * adjusting the underlying data to reflect this move.
+ *
+ * @param fromPosition The start position of the moved item.
+ * @param toPosition Then resolved position of the moved item.
+ * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
+ * @see RecyclerView.ViewHolder#getAdapterPosition()
+ */
+ void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target,
+ int fromPosition, int toPosition);
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java
new file mode 100644
index 000000000..0fd24581d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 Paul Burke
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui.util.recyclerview;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+
+/**
+ * An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and
+ * swipe-to-dismiss. Drag events are automatically started by an item long-press.<br/>
+ * </br/>
+ * Expects the <code>RecyclerView.Adapter</code> to listen for {@link
+ * ItemTouchHelperAdapter} callbacks and the <code>RecyclerView.ViewHolder</code> to implement
+ * {@link ItemTouchHelperViewHolder}.
+ */
+public class ItemTouchHelperDragCallback extends ItemTouchHelper.Callback {
+
+ private final ItemTouchHelperAdapter mAdapter;
+
+ public ItemTouchHelperDragCallback(ItemTouchHelperAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ @Override
+ public boolean isLongPressDragEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isItemViewSwipeEnabled() {
+ return false;
+ }
+
+ @Override
+ public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ // Enable drag and swipe in both directions
+ final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
+ final int swipeFlags = 0;
+ return makeMovementFlags(dragFlags, swipeFlags);
+ }
+
+ @Override
+ public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source,
+ RecyclerView.ViewHolder target) {
+ if (source.getItemViewType() != target.getItemViewType()) {
+ return false;
+ }
+
+ // Notify the adapter of the move
+ mAdapter.onItemMove(source, target, source.getAdapterPosition(), target.getAdapterPosition());
+ return true;
+ }
+
+ @Override
+ public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
+ // we don't support swipe
+ }
+
+ @Override
+ public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
+ if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
+ // Let the view holder know that this item is being moved or dragged
+ ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
+ itemViewHolder.onItemSelected();
+ }
+
+ super.onSelectedChanged(viewHolder, actionState);
+ }
+
+ @Override
+ public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ super.clearView(recyclerView, viewHolder);
+
+ // Tell the view holder it's time to restore the idle state
+ ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
+ itemViewHolder.onItemClear();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java
new file mode 100644
index 000000000..97e70d71e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 Paul Burke
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui.util.recyclerview;
+
+import android.support.v7.widget.helper.ItemTouchHelper;
+
+/**
+ * Interface to notify an item ViewHolder of relevant callbacks from {@link
+ * android.support.v7.widget.helper.ItemTouchHelper.Callback}.
+ */
+public interface ItemTouchHelperViewHolder {
+
+ /**
+ * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped.
+ * Implementations should update the item view to indicate it's active state.
+ */
+ void onItemSelected();
+
+
+ /**
+ * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item
+ * state should be cleared.
+ */
+ void onItemClear();
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java
new file mode 100644
index 000000000..7efcbb30c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 Jacob Tabak
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.sufficientlysecure.keychain.ui.util.recyclerview;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * based on http://stackoverflow.com/a/26196831/3000919
+ */
+public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
+ private OnItemClickListener mListener;
+ private boolean mIgnoreTouch = false;
+
+ public interface OnItemClickListener {
+ void onItemClick(View view, int position);
+ }
+
+ GestureDetector mGestureDetector;
+
+ public RecyclerItemClickListener(Context context, OnItemClickListener listener) {
+ mListener = listener;
+ mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
+ if (mIgnoreTouch) {
+ return false;
+ }
+ View childView = view.findChildViewUnder(e.getX(), e.getY());
+ if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
+ mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) {
+ // TODO: should we move mListener.onItemClick here
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ mIgnoreTouch = disallowIntercept;
+ }
+} \ No newline at end of file