From 50f404ccddd30becf1d69129f493a2620110ed4d Mon Sep 17 00:00:00 2001 From: rohands Date: Sun, 20 Sep 2015 18:57:18 +0530 Subject: Mousehints --- .../keychain/ui/KeyListFragment.java | 3 + .../keychain/ui/ViewKeyActivity.java | 10 +++ .../keychain/ui/util/LongClick.java | 85 ++++++++++++++++++++++ OpenKeychain/src/main/res/values/strings.xml | 4 + 4 files changed, 102 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java 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 ce6994ba4..4e1f41ba0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -67,6 +67,7 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.LongClick; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Log; @@ -706,6 +707,8 @@ public class KeyListFragment extends LoaderFragment final KeyItemViewHolder holder = (KeyItemViewHolder) view.getTag(); holder.mSlinger.setVisibility(View.VISIBLE); + + LongClick.setup(holder.mSlingerButton,getString(R.string.exchange_keys)); holder.mSlingerButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 930c1fc26..f9bde850e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -79,6 +79,7 @@ import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; +import org.sufficientlysecure.keychain.ui.util.LongClick; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Style; @@ -180,6 +181,15 @@ public class ViewKeyActivity extends BaseNfcActivity implements mQrCodeLayout = (CardView) findViewById(R.id.view_key_qr_code_layout); mRotateSpin = AnimationUtils.loadAnimation(this, R.anim.rotate_spin); + + //Long Click Listeners implemented + + LongClick.setup(mActionEncryptFile,getString(R.string.encrypt_files)); + LongClick.setup(mActionEncryptText,getString(R.string.encrypt_text)); + LongClick.setup(mActionNfc,getString(R.string.share_nfc)); + LongClick.setup(mFab,getString(R.string.exchange_keys)); + + mRotateSpin.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java new file mode 100644 index 000000000..c41f77767 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java @@ -0,0 +1,85 @@ +package org.sufficientlysecure.keychain.ui.util; + +/** + * Created by rohan on 20/9/15. + */ +import android.content.Context; +import android.graphics.Rect; +import android.text.TextUtils; +import android.view.Gravity; +import android.view.View; +import android.widget.Toast; +public class LongClick { + private static final int ESTIMATED_TOAST_HEIGHT_DIPS = 48; + public static void setup(View view) { + view.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + return showLongClickText(view, view.getContentDescription()); + } + }); + } + + public static void setup(View view, final int textResId) { + view.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + return showLongClickText(view, view.getContext().getString(textResId)); + } + }); + } + + public static void setup(View view, final CharSequence text) { + view.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + return showLongClickText(view, text); + } + }); + } + + public static void remove(final View view) { + view.setOnLongClickListener(null); + } + + private static boolean showLongClickText(View view, CharSequence text) { + if (TextUtils.isEmpty(text)) { + return false; + } + + final int[] screenPos = new int[2]; // origin is device display + final Rect displayFrame = new Rect(); // includes decorations (e.g. status bar) + view.getLocationOnScreen(screenPos); + view.getWindowVisibleDisplayFrame(displayFrame); + + final Context context = view.getContext(); + final int viewWidth = view.getWidth(); + final int viewHeight = view.getHeight(); + final int viewCenterX = screenPos[0] + viewWidth / 2; + final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; + final int estimatedToastHeight = (int) (ESTIMATED_TOAST_HEIGHT_DIPS + * context.getResources().getDisplayMetrics().density); + + Toast longClickText = Toast.makeText(context, text, Toast.LENGTH_SHORT); + boolean showBelow = screenPos[1] < estimatedToastHeight; + if (showBelow) { + // Show below + // Offsets are after decorations (e.g. status bar) are factored in + longClickText.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, + viewCenterX - screenWidth / 2, + screenPos[1] - displayFrame.top + viewHeight); + } else { + // Show above + // Offsets are after decorations (e.g. status bar) are factored in + // NOTE: We can't use Gravity.BOTTOM because when the keyboard is up + // its height isn't factored in. + longClickText.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, + viewCenterX - screenWidth / 2, + screenPos[1] - displayFrame.top - estimatedToastHeight); + } + + longClickText.show(); + return true; + } + +} diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 62083cbb4..1195e778e 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -99,6 +99,10 @@ "Add" "Save as default" "Saved!" + "Exchange Keys" + "Encrypt Files" + "Encrypt Text" + "Share Via NFC" "Settings" -- cgit v1.2.3 From afbf2b36cf07e3225e186f1ce555927e5240f100 Mon Sep 17 00:00:00 2001 From: rohands Date: Sun, 20 Sep 2015 20:20:43 +0530 Subject: Updated --- .../sufficientlysecure/keychain/ui/KeyListFragment.java | 2 +- .../sufficientlysecure/keychain/ui/ViewKeyActivity.java | 8 ++++---- .../sufficientlysecure/keychain/ui/util/LongClick.java | 16 ++++++++++++++++ OpenKeychain/src/main/res/layout/key_list_item.xml | 3 ++- OpenKeychain/src/main/res/layout/view_key_activity.xml | 4 ++++ 5 files changed, 27 insertions(+), 6 deletions(-) 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 4e1f41ba0..09d2b9fcf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -708,7 +708,7 @@ public class KeyListFragment extends LoaderFragment holder.mSlinger.setVisibility(View.VISIBLE); - LongClick.setup(holder.mSlingerButton,getString(R.string.exchange_keys)); + LongClick.setup(holder.mSlingerButton); holder.mSlingerButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index f9bde850e..6e30f7a22 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -184,10 +184,10 @@ public class ViewKeyActivity extends BaseNfcActivity implements //Long Click Listeners implemented - LongClick.setup(mActionEncryptFile,getString(R.string.encrypt_files)); - LongClick.setup(mActionEncryptText,getString(R.string.encrypt_text)); - LongClick.setup(mActionNfc,getString(R.string.share_nfc)); - LongClick.setup(mFab,getString(R.string.exchange_keys)); + LongClick.setup(mActionEncryptFile); + LongClick.setup(mActionEncryptText); + LongClick.setup(mActionNfc); + LongClick.setup(mFab); mRotateSpin.setAnimationListener(new AnimationListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java index c41f77767..5b0bcbb78 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java @@ -3,6 +3,22 @@ package org.sufficientlysecure.keychain.ui.util; /** * Created by rohan on 20/9/15. */ +/* + * Copyright 2012 Google Inc. + * + * 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. + */ + import android.content.Context; import android.graphics.Rect; import android.text.TextUtils; diff --git a/OpenKeychain/src/main/res/layout/key_list_item.xml b/OpenKeychain/src/main/res/layout/key_list_item.xml index 80be0ec34..88c19c1a6 100644 --- a/OpenKeychain/src/main/res/layout/key_list_item.xml +++ b/OpenKeychain/src/main/res/layout/key_list_item.xml @@ -123,7 +123,8 @@ android:layout_gravity="center" android:src="@drawable/ic_repeat_grey_24dp" android:padding="12dp" - android:background="?android:selectableItemBackground" /> + android:background="?android:selectableItemBackground" + android:contentDescription="@string/exchange_keys"/> diff --git a/OpenKeychain/src/main/res/layout/view_key_activity.xml b/OpenKeychain/src/main/res/layout/view_key_activity.xml index 560180407..a6f9fd17f 100644 --- a/OpenKeychain/src/main/res/layout/view_key_activity.xml +++ b/OpenKeychain/src/main/res/layout/view_key_activity.xml @@ -94,6 +94,7 @@ Date: Tue, 22 Sep 2015 23:06:26 +0530 Subject: Class names and string names are updated --- .../keychain/ui/KeyListFragment.java | 4 +- .../keychain/ui/ViewKeyActivity.java | 12 +-- .../keychain/ui/util/ContentDescriptionHint.java | 101 +++++++++++++++++++++ .../keychain/ui/util/LongClick.java | 101 --------------------- OpenKeychain/src/main/res/layout/key_list_item.xml | 2 +- .../src/main/res/layout/view_key_activity.xml | 8 +- OpenKeychain/src/main/res/values/strings.xml | 10 +- 7 files changed, 120 insertions(+), 118 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ContentDescriptionHint.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java 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 09d2b9fcf..2b6d786d4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -67,7 +67,7 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.LongClick; +import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Log; @@ -708,7 +708,7 @@ public class KeyListFragment extends LoaderFragment holder.mSlinger.setVisibility(View.VISIBLE); - LongClick.setup(holder.mSlingerButton); + ContentDescriptionHint.setup(holder.mSlingerButton); holder.mSlingerButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 6e30f7a22..a09e74abe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -79,7 +79,7 @@ import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; -import org.sufficientlysecure.keychain.ui.util.LongClick; +import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Style; @@ -182,12 +182,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements mRotateSpin = AnimationUtils.loadAnimation(this, R.anim.rotate_spin); - //Long Click Listeners implemented + //ContentDescriptionHint Listeners implemented - LongClick.setup(mActionEncryptFile); - LongClick.setup(mActionEncryptText); - LongClick.setup(mActionNfc); - LongClick.setup(mFab); + ContentDescriptionHint.setup(mActionEncryptFile); + ContentDescriptionHint.setup(mActionEncryptText); + ContentDescriptionHint.setup(mActionNfc); + ContentDescriptionHint.setup(mFab); mRotateSpin.setAnimationListener(new AnimationListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ContentDescriptionHint.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ContentDescriptionHint.java new file mode 100644 index 000000000..8e45a20e9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ContentDescriptionHint.java @@ -0,0 +1,101 @@ +package org.sufficientlysecure.keychain.ui.util; + +/** + * Created by rohan on 20/9/15. + */ +/* + * Copyright 2012 Google Inc. + * + * 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. + */ + +import android.content.Context; +import android.graphics.Rect; +import android.text.TextUtils; +import android.view.Gravity; +import android.view.View; +import android.widget.Toast; +public class ContentDescriptionHint { + private static final int ESTIMATED_TOAST_HEIGHT_DIPS = 48; + public static void setup(View view) { + view.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + return showLongClickText(view, view.getContentDescription()); + } + }); + } + + public static void setup(View view, final int textResId) { + view.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + return showLongClickText(view, view.getContext().getString(textResId)); + } + }); + } + + public static void setup(View view, final CharSequence text) { + view.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + return showLongClickText(view, text); + } + }); + } + + public static void remove(final View view) { + view.setOnLongClickListener(null); + } + + private static boolean showLongClickText(View view, CharSequence text) { + if (TextUtils.isEmpty(text)) { + return false; + } + + final int[] screenPos = new int[2]; // origin is device display + final Rect displayFrame = new Rect(); // includes decorations (e.g. status bar) + view.getLocationOnScreen(screenPos); + view.getWindowVisibleDisplayFrame(displayFrame); + + final Context context = view.getContext(); + final int viewWidth = view.getWidth(); + final int viewHeight = view.getHeight(); + final int viewCenterX = screenPos[0] + viewWidth / 2; + final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; + final int estimatedToastHeight = (int) (ESTIMATED_TOAST_HEIGHT_DIPS + * context.getResources().getDisplayMetrics().density); + + Toast longClickText = Toast.makeText(context, text, Toast.LENGTH_SHORT); + boolean showBelow = screenPos[1] < estimatedToastHeight; + if (showBelow) { + // Show below + // Offsets are after decorations (e.g. status bar) are factored in + longClickText.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, + viewCenterX - screenWidth / 2, + screenPos[1] - displayFrame.top + viewHeight); + } else { + // Show above + // Offsets are after decorations (e.g. status bar) are factored in + // NOTE: We can't use Gravity.BOTTOM because when the keyboard is up + // its height isn't factored in. + longClickText.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, + viewCenterX - screenWidth / 2, + screenPos[1] - displayFrame.top - estimatedToastHeight); + } + + longClickText.show(); + return true; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java deleted file mode 100644 index 5b0bcbb78..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/LongClick.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.sufficientlysecure.keychain.ui.util; - -/** - * Created by rohan on 20/9/15. - */ -/* - * Copyright 2012 Google Inc. - * - * 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. - */ - -import android.content.Context; -import android.graphics.Rect; -import android.text.TextUtils; -import android.view.Gravity; -import android.view.View; -import android.widget.Toast; -public class LongClick { - private static final int ESTIMATED_TOAST_HEIGHT_DIPS = 48; - public static void setup(View view) { - view.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - return showLongClickText(view, view.getContentDescription()); - } - }); - } - - public static void setup(View view, final int textResId) { - view.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - return showLongClickText(view, view.getContext().getString(textResId)); - } - }); - } - - public static void setup(View view, final CharSequence text) { - view.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - return showLongClickText(view, text); - } - }); - } - - public static void remove(final View view) { - view.setOnLongClickListener(null); - } - - private static boolean showLongClickText(View view, CharSequence text) { - if (TextUtils.isEmpty(text)) { - return false; - } - - final int[] screenPos = new int[2]; // origin is device display - final Rect displayFrame = new Rect(); // includes decorations (e.g. status bar) - view.getLocationOnScreen(screenPos); - view.getWindowVisibleDisplayFrame(displayFrame); - - final Context context = view.getContext(); - final int viewWidth = view.getWidth(); - final int viewHeight = view.getHeight(); - final int viewCenterX = screenPos[0] + viewWidth / 2; - final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; - final int estimatedToastHeight = (int) (ESTIMATED_TOAST_HEIGHT_DIPS - * context.getResources().getDisplayMetrics().density); - - Toast longClickText = Toast.makeText(context, text, Toast.LENGTH_SHORT); - boolean showBelow = screenPos[1] < estimatedToastHeight; - if (showBelow) { - // Show below - // Offsets are after decorations (e.g. status bar) are factored in - longClickText.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, - viewCenterX - screenWidth / 2, - screenPos[1] - displayFrame.top + viewHeight); - } else { - // Show above - // Offsets are after decorations (e.g. status bar) are factored in - // NOTE: We can't use Gravity.BOTTOM because when the keyboard is up - // its height isn't factored in. - longClickText.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, - viewCenterX - screenWidth / 2, - screenPos[1] - displayFrame.top - estimatedToastHeight); - } - - longClickText.show(); - return true; - } - -} diff --git a/OpenKeychain/src/main/res/layout/key_list_item.xml b/OpenKeychain/src/main/res/layout/key_list_item.xml index 88c19c1a6..d9e7170c5 100644 --- a/OpenKeychain/src/main/res/layout/key_list_item.xml +++ b/OpenKeychain/src/main/res/layout/key_list_item.xml @@ -124,7 +124,7 @@ android:src="@drawable/ic_repeat_grey_24dp" android:padding="12dp" android:background="?android:selectableItemBackground" - android:contentDescription="@string/exchange_keys"/> + android:contentDescription="@string/cd_exchange_keys"/> diff --git a/OpenKeychain/src/main/res/layout/view_key_activity.xml b/OpenKeychain/src/main/res/layout/view_key_activity.xml index a6f9fd17f..7c021bec2 100644 --- a/OpenKeychain/src/main/res/layout/view_key_activity.xml +++ b/OpenKeychain/src/main/res/layout/view_key_activity.xml @@ -94,7 +94,7 @@ "Add" "Save as default" "Saved!" - "Exchange Keys" - "Encrypt Files" - "Encrypt Text" - "Share Via NFC" + + + "Encrypt Files" + "Exchange Keys" + "Encrypt Text" + "Share Via NFC" "Settings" -- cgit v1.2.3 From 3b95fea379ce4df09f0685463d2c2fb32446326c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 23 Oct 2015 18:26:13 +0200 Subject: decryptlist: implement key lookup (wip) --- .../keychain/ui/DecryptListFragment.java | 111 ++++++++++++++++++--- .../keychain/ui/util/KeyFormattingUtils.java | 41 ++++---- .../src/main/res/layout/decrypt_list_entry.xml | 53 +++++++--- 3 files changed, 154 insertions(+), 51 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 22ef52f6d..8f36a8754 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -67,10 +67,14 @@ import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.InputDataResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.InputDataParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; // this import NEEDS to be above the ViewModel AND SubViewHolder one, or it won't compile! (as of 16.09.15) import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; @@ -84,6 +88,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableHashMap; +import org.sufficientlysecure.keychain.util.Preferences; public class DecryptListFragment @@ -626,6 +631,68 @@ public class DecryptListFragment return false; } + private void lookupUnknownKey(final Uri inputUri, long unknownKeyId) { + + final ArrayList keyList; + final String keyserver; + + // search config + { + Preferences prefs = Preferences.getPreferences(getActivity()); + Preferences.CloudSearchPrefs cloudPrefs = + new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); + keyserver = cloudPrefs.keyserver; + } + + { + ParcelableKeyRing keyEntry = new ParcelableKeyRing(null, + KeyFormattingUtils.convertKeyIdToHex(unknownKeyId), null); + ArrayList selectedEntries = new ArrayList<>(); + selectedEntries.add(keyEntry); + + keyList = selectedEntries; + } + + CryptoOperationHelper.Callback callback + = new CryptoOperationHelper.Callback() { + + @Override + public ImportKeyringParcel createOperationInput() { + return new ImportKeyringParcel(keyList, keyserver); + } + + @Override + public void onCryptoOperationSuccess(ImportKeyResult result) { + // TODO trigger new signature check + result.createNotify(getActivity()).show(); + mAdapter.setProcessingKeyLookup(inputUri, false); + } + + @Override + public void onCryptoOperationCancelled() { + mAdapter.setProcessingKeyLookup(inputUri, false); + } + + @Override + public void onCryptoOperationError(ImportKeyResult result) { + result.createNotify(getActivity()).show(); + mAdapter.setProcessingKeyLookup(inputUri, false); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + + mAdapter.setProcessingKeyLookup(inputUri, true); + + CryptoOperationHelper importOpHelper = new CryptoOperationHelper<>(2, this, callback, null); + importOpHelper.cryptoOperation(); + + } + + private void deleteFile(Activity activity, Uri uri) { // we can only ever delete a file once, if we got this far either it's gone or it will never work @@ -671,6 +738,7 @@ public class DecryptListFragment int mProgress, mMax; String mProgressMsg; OnClickListener mCancelled; + boolean mProcessingKeyLookup; ViewModel(Uri uri) { mInputUri = uri; @@ -699,6 +767,10 @@ public class DecryptListFragment mMax = max; } + void setProcessingKeyLookup(boolean processingKeyLookup) { + mProcessingKeyLookup = processingKeyLookup; + } + // Depends on inputUri only @Override public boolean equals(Object o) { @@ -765,17 +837,13 @@ public class DecryptListFragment } private void bindItemCancelled(ViewHolder holder, ViewModel model) { - if (holder.vAnimator.getDisplayedChild() != 3) { - holder.vAnimator.setDisplayedChild(3); - } + holder.vAnimator.setDisplayedChild(3); holder.vCancelledRetry.setOnClickListener(model.mCancelled); } private void bindItemProgress(ViewHolder holder, ViewModel model) { - if (holder.vAnimator.getDisplayedChild() != 0) { - holder.vAnimator.setDisplayedChild(0); - } + holder.vAnimator.setDisplayedChild(0); holder.vProgress.setProgress(model.mProgress); holder.vProgress.setMax(model.mMax); @@ -785,11 +853,10 @@ public class DecryptListFragment } private void bindItemSuccess(ViewHolder holder, final ViewModel model) { - if (holder.vAnimator.getDisplayedChild() != 1) { - holder.vAnimator.setDisplayedChild(1); - } + holder.vAnimator.setDisplayedChild(1); - KeyFormattingUtils.setStatus(getResources(), holder, model.mResult.mDecryptVerifyResult); + KeyFormattingUtils.setStatus(getResources(), holder, + model.mResult.mDecryptVerifyResult, model.mProcessingKeyLookup); int numFiles = model.mResult.getOutputUris().size(); holder.resizeFileList(numFiles, LayoutInflater.from(getActivity())); @@ -867,6 +934,13 @@ public class DecryptListFragment activity.startActivity(intent); } }); + } else { + holder.vSignatureLayout.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + lookupUnknownKey(model.mInputUri, keyId); + } + }); } } @@ -895,9 +969,7 @@ public class DecryptListFragment } private void bindItemFailure(ViewHolder holder, final ViewModel model) { - if (holder.vAnimator.getDisplayedChild() != 2) { - holder.vAnimator.setDisplayedChild(2); - } + holder.vAnimator.setDisplayedChild(2); holder.vErrorMsg.setText(model.mResult.getLog().getLast().mType.getMsgId()); @@ -953,6 +1025,13 @@ public class DecryptListFragment notifyItemChanged(pos); } + public void setProcessingKeyLookup(Uri uri, boolean processingKeyLookup) { + ViewModel newModel = new ViewModel(uri); + int pos = mDataset.indexOf(newModel); + mDataset.get(pos).setProcessingKeyLookup(processingKeyLookup); + notifyItemChanged(pos); + } + public void addResult(Uri uri, InputDataResult result) { ViewModel model = new ViewModel(uri); @@ -984,7 +1063,7 @@ public class DecryptListFragment public View vSignatureLayout; public TextView vSignatureName; public TextView vSignatureMail; - public TextView vSignatureAction; + public ViewAnimator vSignatureAction; public View vContextMenu; public TextView vErrorMsg; @@ -1027,7 +1106,7 @@ public class DecryptListFragment vSignatureLayout = itemView.findViewById(R.id.result_signature_layout); vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name); vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email); - vSignatureAction = (TextView) itemView.findViewById(R.id.result_signature_action); + vSignatureAction = (ViewAnimator) itemView.findViewById(R.id.result_signature_action); vFileList = (LinearLayout) itemView.findViewById(R.id.file_list); for (int i = 0; i < vFileList.getChildCount(); i++) { @@ -1091,7 +1170,7 @@ public class DecryptListFragment } @Override - public TextView getSignatureAction() { + public ViewAnimator getSignatureAction() { return vSignatureAction; } 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 9ab0db03e..b9b837d71 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 @@ -28,6 +28,7 @@ import android.text.style.ForegroundColorSpan; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import android.widget.ViewAnimator; import org.openintents.openpgp.OpenPgpDecryptionResult; import org.openintents.openpgp.OpenPgpSignatureResult; @@ -440,14 +441,15 @@ public class KeyFormattingUtils { View getSignatureLayout(); TextView getSignatureUserName(); TextView getSignatureUserEmail(); - TextView getSignatureAction(); + ViewAnimator getSignatureAction(); boolean hasEncrypt(); } @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated - public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result) { + public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result, + boolean processingkeyLookup) { if (holder.hasEncrypt()) { OpenPgpDecryptionResult decryptionResult = result.getDecryptionResult(); @@ -488,7 +490,7 @@ public class KeyFormattingUtils { OpenPgpSignatureResult signatureResult = result.getSignatureResult(); int sigText, sigIcon, sigColor; - int sigActionText, sigActionIcon; + int sigActionDisplayedChild; switch (signatureResult.getResult()) { @@ -500,8 +502,7 @@ public class KeyFormattingUtils { sigColor = R.color.key_flag_gray; // won't be used, but makes compiler happy - sigActionText = 0; - sigActionIcon = 0; + sigActionDisplayedChild = -1; break; } @@ -510,8 +511,7 @@ public class KeyFormattingUtils { 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; + sigActionDisplayedChild = 0; break; } @@ -520,8 +520,7 @@ public class KeyFormattingUtils { 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; + sigActionDisplayedChild = 0; break; } @@ -530,8 +529,7 @@ public class KeyFormattingUtils { 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; + sigActionDisplayedChild = 0; break; } @@ -540,8 +538,7 @@ public class KeyFormattingUtils { 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; + sigActionDisplayedChild = 0; break; } @@ -550,8 +547,7 @@ public class KeyFormattingUtils { 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; + sigActionDisplayedChild = 1; break; } @@ -560,8 +556,7 @@ public class KeyFormattingUtils { 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; + sigActionDisplayedChild = 0; break; } @@ -572,13 +567,17 @@ public class KeyFormattingUtils { sigColor = R.color.key_flag_red; // won't be used, but makes compiler happy - sigActionText = 0; - sigActionIcon = 0; + sigActionDisplayedChild = -1; break; } } + // possibly switch out "Lookup" button for progress bar + if (sigActionDisplayedChild == 1 && processingkeyLookup) { + sigActionDisplayedChild = 2; + } + int sigColorRes = resources.getColor(sigColor); holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN); holder.getSignatureStatusIcon().setImageDrawable(resources.getDrawable(sigIcon)); @@ -591,9 +590,7 @@ public class KeyFormattingUtils { holder.getSignatureLayout().setVisibility(View.VISIBLE); - holder.getSignatureAction().setText(sigActionText); - holder.getSignatureAction().setCompoundDrawablesWithIntrinsicBounds( - 0, 0, sigActionIcon, 0); + holder.getSignatureAction().setDisplayedChild(sigActionDisplayedChild); String userId = result.getSignatureResult().getPrimaryUserId(); KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId); diff --git a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml index 4a79f0ccd..90de70f3c 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml @@ -26,7 +26,6 @@ android:measureAllChildren="false" custom:initialView="1" android:minHeight="?listPreferredItemHeightSmall" - android:animateLayoutChanges="true" > @@ -136,6 +136,7 @@ android:clickable="true" android:background="?android:selectableItemBackground" android:orientation="horizontal" + android:minHeight="40dp" > - + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:id="@+id/result_signature_action" + android:measureAllChildren="true" + android:inAnimation="@anim/fade_in" + android:outAnimation="@anim/fade_out" + custom:initialView="0" + > + + + + + + + + -- cgit v1.2.3 From cf51366bb7863f68989e30dba86a0d2dc1e41ce3 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 23 Oct 2015 18:49:31 +0200 Subject: decryptlist: re-decrypt after key lookup --- .../keychain/ui/DecryptListFragment.java | 22 ++++++++++++++-------- .../src/main/res/layout/decrypt_list_entry.xml | 2 ++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 8f36a8754..1d2bf6b9c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -459,8 +459,9 @@ public class DecryptListFragment // un-cancel this one mCancelledInputUris.remove(uri); + mInputDataResults.remove(uri); mPendingInputUris.add(uri); - mAdapter.setCancelled(uri, null); + mAdapter.resetItemData(uri); cryptoOperation(); @@ -663,9 +664,7 @@ public class DecryptListFragment @Override public void onCryptoOperationSuccess(ImportKeyResult result) { - // TODO trigger new signature check - result.createNotify(getActivity()).show(); - mAdapter.setProcessingKeyLookup(inputUri, false); + retryUri(inputUri); } @Override @@ -747,7 +746,7 @@ public class DecryptListFragment mCancelled = null; } - void addResult(InputDataResult result) { + void setResult(InputDataResult result) { mResult = result; } @@ -1033,13 +1032,20 @@ public class DecryptListFragment } public void addResult(Uri uri, InputDataResult result) { - ViewModel model = new ViewModel(uri); int pos = mDataset.indexOf(model); model = mDataset.get(pos); + model.setResult(result); + notifyItemChanged(pos); + } - model.addResult(result); - + public void resetItemData(Uri uri) { + ViewModel model = new ViewModel(uri); + int pos = mDataset.indexOf(model); + model = mDataset.get(pos); + model.setResult(null); + model.setCancelled(null); + model.setProcessingKeyLookup(false); notifyItemChanged(pos); } diff --git a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml index 90de70f3c..8bdfce733 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml @@ -163,6 +163,8 @@ android:layout_height="wrap_content" android:gravity="center_vertical" android:text="" + android:singleLine="true" + android:ellipsize="end" tools:text="alice@example.com" /> -- cgit v1.2.3 From ed3a7c259e14de57d0eb9a85c9406e9d7521f40e Mon Sep 17 00:00:00 2001 From: Making GitHub Delicious Date: Fri, 13 Nov 2015 15:26:36 -0700 Subject: add waffle.io badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3144b4b77..f3e839611 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Stories in Ready](https://badge.waffle.io/open-keychain/open-keychain.png?label=ready&title=Ready)](https://waffle.io/open-keychain/open-keychain) # OpenKeychain (for Android) OpenKeychain is an OpenPGP implementation for Android. -- cgit v1.2.3 From e92bd4bea919b6bf9ce9392133b59d9af4678d74 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 14 Nov 2015 17:43:52 +0100 Subject: decryptlist: some cleanup and streamlining of control flow --- .../keychain/ui/DecryptListFragment.java | 165 +++++++++++---------- 1 file changed, 84 insertions(+), 81 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index dbee564b1..737a5b3b6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -22,7 +22,6 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import android.Manifest; @@ -103,7 +102,7 @@ public class DecryptListFragment public static final String ARG_CAN_DELETE = "can_delete"; private static final int REQUEST_CODE_OUTPUT = 0x00007007; - private static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 12; + private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; private ArrayList mInputUris; private HashMap mInputDataResults; @@ -215,12 +214,7 @@ public class DecryptListFragment mAdapter.add(uri); if (mCancelledInputUris.contains(uri)) { - mAdapter.setCancelled(uri, new OnClickListener() { - @Override - public void onClick(View v) { - retryUri(uri); - } - }); + mAdapter.setCancelled(uri, true); continue; } @@ -362,12 +356,7 @@ public class DecryptListFragment mCurrentInputUri = null; mCancelledInputUris.add(uri); - mAdapter.setCancelled(uri, new OnClickListener() { - @Override - public void onClick(View v) { - retryUri(uri); - } - }); + mAdapter.setCancelled(uri, true); cryptoOperation(); @@ -458,7 +447,7 @@ public class DecryptListFragment // un-cancel this one mCancelledInputUris.remove(uri); mPendingInputUris.add(uri); - mAdapter.setCancelled(uri, null); + mAdapter.setCancelled(uri, false); // check if there are any pending input uris cryptoOperation(); @@ -582,6 +571,11 @@ public class DecryptListFragment @Override public InputDataParcel createOperationInput() { + Activity activity = getActivity(); + if (activity == null) { + return null; + } + if (mCurrentInputUri == null) { if (mPendingInputUris.isEmpty()) { // nothing left to do @@ -593,95 +587,95 @@ public class DecryptListFragment Log.d(Constants.TAG, "mCurrentInputUri=" + mCurrentInputUri); - if (readPermissionGranted(mCurrentInputUri)) { - PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel() - .setAllowSymmetricDecryption(true); - return new InputDataParcel(mCurrentInputUri, decryptInput); - } else { + if ( ! checkAndRequestReadPermission(activity, mCurrentInputUri)) { return null; } + + PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel() + .setAllowSymmetricDecryption(true); + return new InputDataParcel(mCurrentInputUri, decryptInput); + } /** - * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris + * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. + * + * This method returns true on Android < 6, or if permission is already granted. It + * requests the permission and returns false otherwise, taking over responsibility + * for mCurrentInputUri. * - * see - * https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html + * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html */ - private boolean readPermissionGranted(final Uri uri) { + private boolean checkAndRequestReadPermission(Activity activity, final Uri uri) { + if ( ! "file".equals(uri.getScheme())) { + return true; + } + if (Build.VERSION.SDK_INT < VERSION_CODES.M) { return true; } - if (! "file".equals(uri.getScheme())) { + + // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { return true; } - // Build check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN || - ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { return true; - } else { - requestPermissions( - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, - MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); - - mCurrentInputUri = null; - mCancelledInputUris.add(uri); - mAdapter.setCancelled(uri, new OnClickListener() { - @Override - public void onClick(View v) { - retryUri(uri); - } - }); - return false; } + + requestPermissions( + new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, + REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); + + return false; + } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { - switch (requestCode) { - case MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: { - if (grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - - // permission granted -> retry all cancelled file uris! - for (Iterator iterator = mCancelledInputUris.iterator(); iterator.hasNext(); ) { - Uri uri = iterator.next(); - - if ("file".equals(uri.getScheme())) { - iterator.remove(); - mPendingInputUris.add(uri); - mAdapter.setCancelled(uri, null); - } - } + public void onRequestPermissionsResult(int requestCode, + @NonNull String[] permissions, + @NonNull int[] grantResults) { - // check if there are any pending input uris - cryptoOperation(); - } else { + if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + return; + } - // permission denied -> cancel all pending file uris - mCurrentInputUri = null; - for (final Uri uri : mPendingInputUris) { - if ("file".equals(uri.getScheme())) { - if (! mCancelledInputUris.contains(uri)) { - mCancelledInputUris.add(uri); - } - mAdapter.setCancelled(uri, new OnClickListener() { - @Override - public void onClick(View v) { - retryUri(uri); - } - }); - } - } + boolean permissionWasGranted = grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED; + + if (permissionWasGranted) { + + // permission granted -> retry all cancelled file uris + for (Uri uri : mCancelledInputUris) { + if ( ! "file".equals(uri.getScheme())) { + continue; } + mCancelledInputUris.remove(uri); + mPendingInputUris.add(uri); + mAdapter.setCancelled(uri, false); } - default: { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + } else { + + // permission denied -> cancel current, and all pending file uris + mCurrentInputUri = null; + for (final Uri uri : mPendingInputUris) { + if ( ! "file".equals(uri.getScheme())) { + continue; + } + mPendingInputUris.remove(uri); + mCancelledInputUris.add(uri); + mAdapter.setCancelled(uri, true); } + } + + // hand control flow back + cryptoOperation(); + } @Override @@ -1034,10 +1028,19 @@ public class DecryptListFragment notifyItemChanged(pos); } - public void setCancelled(Uri uri, OnClickListener retryListener) { + public void setCancelled(final Uri uri, boolean isCancelled) { ViewModel newModel = new ViewModel(uri); int pos = mDataset.indexOf(newModel); - mDataset.get(pos).setCancelled(retryListener); + if (isCancelled) { + mDataset.get(pos).setCancelled(new OnClickListener() { + @Override + public void onClick(View v) { + retryUri(uri); + } + }); + } else { + mDataset.get(pos).setCancelled(null); + } notifyItemChanged(pos); } -- cgit v1.2.3 From d263bade92c2161ad7759f471341647b4fe2d580 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 14 Nov 2015 18:28:25 +0100 Subject: decryptlist: minor cleanups and documentation --- .../keychain/ui/DecryptListFragment.java | 37 ++++++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 737a5b3b6..7db39af6f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -91,6 +91,22 @@ import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableHashMap; +/** Displays a list of decrypted inputs. + * + * This class has a complex control flow to manage its input URIs. Each URI + * which is in mInputUris is also in exactly one of mPendingInputUris, + * mCancelledInputUris, mCurrentInputUri, or a key in mInputDataResults. + * + * Processing of URIs happens using a looping approach: + * - There is always exactly one method running which works on mCurrentInputUri + * - Processing starts in cryptoOperation(), which pops a new mCurrentInputUri + * from the list of mPendingInputUris. + * - Once a mCurrentInputUri is finished processing, it should be set to null and + * control handed back to cryptoOperation() + * - Control flow can move through asynchronous calls, and resume in callbacks + * like onActivityResult() or onPermissionRequestResult(). + * + */ public class DecryptListFragment extends QueueingCryptoOperationFragment implements OnMenuItemClickListener { @@ -200,7 +216,9 @@ public class DecryptListFragment ); } - private void displayInputUris(ArrayList inputUris, ArrayList cancelledUris, + private void displayInputUris( + ArrayList inputUris, + ArrayList cancelledUris, HashMap results) { mInputUris = inputUris; @@ -213,16 +231,19 @@ public class DecryptListFragment for (final Uri uri : inputUris) { mAdapter.add(uri); - if (mCancelledInputUris.contains(uri)) { + boolean uriIsCancelled = mCancelledInputUris.contains(uri); + if (uriIsCancelled) { mAdapter.setCancelled(uri, true); continue; } - if (results != null && results.containsKey(uri)) { + boolean uriHasResult = results != null && results.containsKey(uri); + if (uriHasResult) { processResult(uri); - } else { - mPendingInputUris.add(uri); + continue; } + + mPendingInputUris.add(uri); } // check if there are any pending input uris @@ -791,8 +812,10 @@ public class DecryptListFragment return false; } ViewModel viewModel = (ViewModel) o; - return !(mInputUri != null ? !mInputUri.equals(viewModel.mInputUri) - : viewModel.mInputUri != null); + if (mInputUri == null) { + return viewModel.mInputUri == null; + } + return mInputUri.equals(viewModel.mInputUri); } // Depends on inputUri only -- cgit v1.2.3 From ba2c5c3bd0a2c4b6c7f0f0af74a6fd22e6213c4a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 14 Nov 2015 23:37:29 +0100 Subject: inputdataop: leave content as-is if no header found (fixes #1592) --- .../keychain/operations/InputDataOperation.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index a09cf4f27..d3f4cd2f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -165,6 +165,7 @@ public class InputDataOperation extends BaseOperation { parser.setContentDecoding(true); parser.setRecurse(); parser.setContentHandler(new AbstractContentHandler() { + private boolean mFoundHeaderWithFields = false; private Uri uncheckedSignedDataUri; String mFilename; @@ -220,12 +221,20 @@ public class InputDataOperation extends BaseOperation { mFilename = null; } + @Override + public void endHeader() throws MimeException { + if ( ! mFoundHeaderWithFields) { + parser.stop(); + } + } + @Override public void field(Field field) throws MimeException { field = DefaultFieldParser.getParser().parse(field, DecodeMonitor.SILENT); if (field instanceof ContentDispositionField) { mFilename = ((ContentDispositionField) field).getFilename(); } + mFoundHeaderWithFields = true; } private void bodySignature(BodyDescriptor bd, InputStream is) throws MimeException, IOException { -- cgit v1.2.3 From 529b9518c12208f281af15945d06389050abd887 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 15 Nov 2015 01:01:05 +0100 Subject: decrypt: skip all encountered marker packets (fix #1582) --- .../jcajce/JcaSkipMarkerPGPObjectFactory.java | 24 ++++++++++++++++++++++ .../keychain/operations/InputDataOperation.java | 5 ++--- .../operations/results/OperationResult.java | 1 - .../keychain/pgp/PgpDecryptVerifyOperation.java | 16 +++++++-------- .../keychain/provider/KeychainProvider.java | 1 + OpenKeychain/src/main/res/values/strings.xml | 1 - 6 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java new file mode 100644 index 000000000..a60c8e499 --- /dev/null +++ b/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java @@ -0,0 +1,24 @@ +package org.spongycastle.openpgp.jcajce; + + +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.openpgp.PGPMarker; + + +public class JcaSkipMarkerPGPObjectFactory extends JcaPGPObjectFactory { + + public JcaSkipMarkerPGPObjectFactory(InputStream in) { + super(in); + } + + @Override + public Object nextObject() throws IOException { + Object o = super.nextObject(); + while (o instanceof PGPMarker) { + o = super.nextObject(); + } + return o; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index d3f4cd2f3..bb8d6ad73 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -110,10 +110,9 @@ public class InputDataOperation extends BaseOperation { if (decryptResult.isPending()) { return new InputDataResult(log, decryptResult); } - log.addByMerge(decryptResult, 2); + log.addByMerge(decryptResult, 1); - if (!decryptResult.success()) { - log.add(LogType.MSG_DATA_ERROR_OPENPGP, 1); + if ( ! decryptResult.success()) { return new InputDataResult(InputDataResult.RESULT_ERROR, log); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 0f9f45cbf..9877f2318 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -836,7 +836,6 @@ public abstract class OperationResult implements Parcelable { MSG_DATA (LogLevel.START, R.string.msg_data), MSG_DATA_OPENPGP (LogLevel.DEBUG, R.string.msg_data_openpgp), MSG_DATA_ERROR_IO (LogLevel.ERROR, R.string.msg_data_error_io), - MSG_DATA_ERROR_OPENPGP (LogLevel.ERROR, R.string.msg_data_error_openpgp), MSG_DATA_DETACHED (LogLevel.INFO, R.string.msg_data_detached), MSG_DATA_DETACHED_CLEAR (LogLevel.WARN, R.string.msg_data_detached_clear), MSG_DATA_DETACHED_SIG (LogLevel.DEBUG, R.string.msg_data_detached_sig), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 1511fd5b1..ea7465209 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -48,7 +48,7 @@ import org.spongycastle.openpgp.PGPPBEEncryptedData; import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; import org.spongycastle.openpgp.PGPSignatureList; import org.spongycastle.openpgp.PGPUtil; -import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory; +import org.spongycastle.openpgp.jcajce.JcaSkipMarkerPGPObjectFactory; import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory; @@ -281,11 +281,11 @@ public class PgpDecryptVerifyOperation extends BaseOperation"Skipping trailing data after signed part!" "Unsupported type of detached signature!" "Error reading input data!" - "Error processing OpenPGP data!" "Could not parse as MIME data" "Filename: '%s'" "Guessing MIME type from extension" -- cgit v1.2.3 From 0456caedf43b2903731a669690333b07f217b8c6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 15 Nov 2015 02:18:39 +0100 Subject: provider: add debug variable to explain query plans --- .../org/sufficientlysecure/keychain/Constants.java | 1 + .../keychain/provider/KeychainProvider.java | 32 +++++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 79175e9ad..69f1862ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -28,6 +28,7 @@ public final class Constants { public static final boolean DEBUG = BuildConfig.DEBUG; public static final boolean DEBUG_LOG_DB_QUERIES = false; + public static final boolean DEBUG_EXPLAIN_QUERIES = false; public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false; public static final boolean DEBUG_KEYSERVER_SYNC = false; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 6359e71dd..70e44d37a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -719,14 +719,38 @@ public class KeychainProvider extends ContentProvider { cursor.setNotificationUri(getContext().getContentResolver(), uri); } + Log.d(Constants.TAG, + "Query: " + qb.buildQuery(projection, selection, null, null, orderBy, null)); + if (Constants.DEBUG && Constants.DEBUG_LOG_DB_QUERIES) { - Log.d(Constants.TAG, - "Query: " - + qb.buildQuery(projection, selection, selectionArgs, null, null, - orderBy, null)); Log.d(Constants.TAG, "Cursor: " + DatabaseUtils.dumpCursorToString(cursor)); } + if (Constants.DEBUG && Constants.DEBUG_EXPLAIN_QUERIES) { + String rawQuery = qb.buildQuery(projection, selection, groupBy, having, orderBy, null); + Cursor explainCursor = db.rawQuery("EXPLAIN QUERY PLAN " + rawQuery, selectionArgs); + + // this is a debugging feature, we can be a little careless + explainCursor.moveToFirst(); + + StringBuilder line = new StringBuilder(); + for (int i = 0; i < explainCursor.getColumnCount(); i++) { + line.append(explainCursor.getColumnName(i)).append(", "); + } + Log.d(Constants.TAG, line.toString()); + + while (!explainCursor.isAfterLast()) { + line = new StringBuilder(); + for (int i = 0; i < explainCursor.getColumnCount(); i++) { + line.append(explainCursor.getString(i)).append(", "); + } + Log.d(Constants.TAG, line.toString()); + explainCursor.moveToNext(); + } + + explainCursor.close(); + } + return cursor; } -- cgit v1.2.3 From abfa7d743cf159a25f54ddcdc84d375c7a9df21e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 15 Nov 2015 02:45:21 +0100 Subject: some optimizations and indexes for the main key list query --- .../keychain/provider/KeychainDatabase.java | 22 ++++++++++++++++------ .../keychain/provider/KeychainProvider.java | 14 ++++++-------- .../keychain/ui/KeyListFragment.java | 2 +- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 0f90f8141..ff5e64bb6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -54,7 +54,7 @@ import java.io.IOException; */ public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 13; + private static final int DATABASE_VERSION = 14; static Boolean apgHack = false; private Context mContext; @@ -74,12 +74,14 @@ public class KeychainDatabase extends SQLiteOpenHelper { "CREATE TABLE IF NOT EXISTS keyrings_public (" + KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY," + KeyRingsColumns.KEY_RING_DATA + " BLOB" + + "PRIMARY KEY(" + KeyRingsColumns.MASTER_KEY_ID + ")," + ")"; private static final String CREATE_KEYRINGS_SECRET = "CREATE TABLE IF NOT EXISTS keyrings_secret (" + KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY," + KeyRingsColumns.KEY_RING_DATA + " BLOB," + + "PRIMARY KEY(" + KeyRingsColumns.MASTER_KEY_ID + ")," + "FOREIGN KEY(" + KeyRingsColumns.MASTER_KEY_ID + ") " + "REFERENCES keyrings_public(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" + ")"; @@ -220,6 +222,13 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_API_APPS_ACCOUNTS); db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); + + db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");"); + db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", " + + UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");"); + db.execSQL("CREATE INDEX verified_certs ON certs (" + + CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");"); + } @Override @@ -291,13 +300,14 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3"); case 12: db.execSQL(CREATE_UPDATE_KEYS); - if (oldVersion == 10) { - // no consolidate if we are updating from 10, we're just here for - // the api_accounts fix and the new update keys table - return; - } case 13: // do nothing here, just consolidate + case 14: + db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");"); + db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", " + + UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");"); + db.execSQL("CREATE INDEX verified_certs ON certs (" + + CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 70e44d37a..104343074 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -303,14 +303,14 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT); projectionMap.put(KeyRings.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID); projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID, - "(SELECT COUNT (*) FROM " + Tables.USER_PACKETS + " AS dups" + "(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups" + " WHERE dups." + UserPackets.MASTER_KEY_ID + " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " AND dups." + UserPackets.RANK + " = 0" + " AND dups." + UserPackets.USER_ID + " = "+ Tables.USER_PACKETS + "." + UserPackets.USER_ID - + ") AS " + KeyRings.HAS_DUPLICATE_USER_ID); - projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED); + + ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID); + projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); projectionMap.put(KeyRings.PUBKEY_DATA, Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.KEY_RING_DATA + " AS " + KeyRings.PUBKEY_DATA); @@ -319,10 +319,8 @@ public class KeychainProvider extends ContentProvider { + " AS " + KeyRings.PRIVKEY_DATA); projectionMap.put(KeyRings.HAS_SECRET, Tables.KEYS + "." + KeyRings.HAS_SECRET); projectionMap.put(KeyRings.HAS_ANY_SECRET, - "(EXISTS (SELECT * FROM " + Tables.KEY_RINGS_SECRET - + " WHERE " + Tables.KEY_RINGS_SECRET + "." + KeyRingData.MASTER_KEY_ID - + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + ")) AS " + KeyRings.HAS_ANY_SECRET); + "(" + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NOT NULL)" + + " AS " + KeyRings.HAS_ANY_SECRET); projectionMap.put(KeyRings.HAS_ENCRYPT, "kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT); projectionMap.put(KeyRings.HAS_SIGN, @@ -363,7 +361,7 @@ public class KeychainProvider extends ContentProvider { + " = " + Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.MASTER_KEY_ID + ")" : "") - + (plist.contains(KeyRings.PRIVKEY_DATA) ? + + (plist.contains(KeyRings.PRIVKEY_DATA) || plist.contains(KeyRings.HAS_ANY_SECRET) ? " LEFT JOIN " + Tables.KEY_RINGS_SECRET + " ON (" + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = " 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 2735eb6b8..23c1250d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -298,7 +298,7 @@ public class KeyListFragment extends LoaderFragment } static final String ORDER = - KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC"; + KeyRings.HAS_ANY_SECRET + " DESC, " + KeyRings.USER_ID + " COLLATE NOCASE ASC"; @Override public Loader onCreateLoader(int id, Bundle args) { -- cgit v1.2.3 From a41e6e0c705e8c927d1f905fad9b36e810dc5acc Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 15 Nov 2015 03:10:30 +0100 Subject: allow database downgrade for debug builds --- .../org/sufficientlysecure/keychain/provider/KeychainDatabase.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index ff5e64bb6..56081dab9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -320,6 +320,10 @@ public class KeychainDatabase extends SQLiteOpenHelper { @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // Downgrade is ok for the debug version, makes it easier to work with branches + if (Constants.DEBUG) { + return; + } // NOTE: downgrading the database is explicitly not allowed to prevent // someone from exploiting old bugs to export the database throw new RuntimeException("Downgrading the database is not allowed!"); -- cgit v1.2.3 From 79436044354651b3254000f102d14ccf1192b4b2 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 15 Nov 2015 03:36:30 +0100 Subject: fix accidental commit (thought I had removed that) --- .../org/sufficientlysecure/keychain/provider/KeychainDatabase.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 56081dab9..752c13007 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -74,14 +74,12 @@ public class KeychainDatabase extends SQLiteOpenHelper { "CREATE TABLE IF NOT EXISTS keyrings_public (" + KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY," + KeyRingsColumns.KEY_RING_DATA + " BLOB" - + "PRIMARY KEY(" + KeyRingsColumns.MASTER_KEY_ID + ")," + ")"; private static final String CREATE_KEYRINGS_SECRET = "CREATE TABLE IF NOT EXISTS keyrings_secret (" + KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY," - + KeyRingsColumns.KEY_RING_DATA + " BLOB," - + "PRIMARY KEY(" + KeyRingsColumns.MASTER_KEY_ID + ")," + + KeyRingsColumns.KEY_RING_DATA + " BLOB, " + "FOREIGN KEY(" + KeyRingsColumns.MASTER_KEY_ID + ") " + "REFERENCES keyrings_public(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" + ")"; -- cgit v1.2.3 From bb79b44e7f9ecd7f0c8d646df14ab63091b9b85a Mon Sep 17 00:00:00 2001 From: Vincent Date: Sun, 15 Nov 2015 20:33:47 +0100 Subject: add documentation on marker packet skipping --- .../openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java index a60c8e499..f1cf9791a 100644 --- a/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java +++ b/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java @@ -6,7 +6,15 @@ import java.io.InputStream; import org.spongycastle.openpgp.PGPMarker; - +/** This class wraps the regular PGPObjectFactory, changing its behavior to + * ignore all PGPMarker packets it encounters while reading. These packets + * carry no semantics of their own, and should be ignored according to + * RFC 4880. + * + * @see https://tools.ietf.org/html/rfc4880#section-5.8 + * @see org.spongycastle.openpgp.PGPMarker + * + */ public class JcaSkipMarkerPGPObjectFactory extends JcaPGPObjectFactory { public JcaSkipMarkerPGPObjectFactory(InputStream in) { -- cgit v1.2.3 From c4599798f9807c0cc692e1b08355892136ab317c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 15 Nov 2015 05:41:14 +0100 Subject: fix delete file securely method and use for delete original file --- .../sufficientlysecure/keychain/pgp/PgpHelper.java | 45 ++--------------- .../keychain/ui/DecryptListFragment.java | 27 +++-------- .../keychain/util/FileHelper.java | 56 ++++++++++++++++++---- 3 files changed, 58 insertions(+), 70 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java index fbda90775..016651c3b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -18,23 +18,15 @@ package org.sufficientlysecure.keychain.pgp; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import android.support.annotation.NonNull; import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Preferences; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.security.SecureRandom; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class PgpHelper { @@ -50,35 +42,6 @@ public class PgpHelper { ".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*", Pattern.DOTALL); - /** - * Deletes file securely by overwriting it with random data before deleting it. - *

- * TODO: Does this really help on flash storage? - * - * @throws IOException - */ - public static void deleteFileSecurely(Context context, Progressable progressable, File file) - throws IOException { - long length = file.length(); - SecureRandom random = new SecureRandom(); - RandomAccessFile raf = new RandomAccessFile(file, "rws"); - raf.seek(0); - raf.getFilePointer(); - byte[] data = new byte[1 << 16]; - int pos = 0; - String msg = context.getString(R.string.progress_deleting_securely, file.getName()); - while (pos < length) { - if (progressable != null) { - progressable.setProgress(msg, (int) (100 * pos / length), 100); - } - random.nextBytes(data); - raf.write(data); - pos += data.length; - } - raf.close(); - file.delete(); - } - /** * Fixing broken PGP MESSAGE Strings coming from GMail/AOSP Mail */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index c45a641e0..000de6e40 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -800,33 +800,18 @@ public class DecryptListFragment // we can only ever delete a file once, if we got this far either it's gone or it will never work mCanDelete = false; - if ("file".equals(uri.getScheme())) { - File file = new File(uri.getPath()); - if (file.delete()) { + try { + int deleted = FileHelper.deleteFileSecurely(activity, uri); + if (deleted > 0) { Notify.create(activity, R.string.file_delete_ok, Style.OK).show(); } else { Notify.create(activity, R.string.file_delete_none, Style.WARN).show(); } - return; + } catch (Exception e) { + Log.e(Constants.TAG, "exception deleting file", e); + Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show(); } - if ("content".equals(uri.getScheme())) { - try { - int deleted = activity.getContentResolver().delete(uri, null, null); - if (deleted > 0) { - Notify.create(activity, R.string.file_delete_ok, Style.OK).show(); - } else { - Notify.create(activity, R.string.file_delete_none, Style.WARN).show(); - } - } catch (Exception e) { - Log.e(Constants.TAG, "exception deleting file", e); - Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show(); - } - return; - } - - Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show(); - } public class DecryptFilesAdapter extends RecyclerView.Adapter { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java index 7345faad9..bae119700 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java @@ -25,7 +25,9 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.security.SecureRandom; import java.text.DecimalFormat; import android.annotation.TargetApi; @@ -33,7 +35,6 @@ import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Point; @@ -41,20 +42,13 @@ import android.net.Uri; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Environment; -import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; import android.provider.OpenableColumns; import android.support.v4.app.Fragment; -import android.system.ErrnoException; -import android.system.Os; -import android.system.StructStat; import android.widget.Toast; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import static android.system.OsConstants.S_IROTH; - /** This class offers a number of helper functions for saving documents. * * There are three entry points here: openDocument, saveDocument and @@ -167,6 +161,14 @@ public class FileHelper { } public static long getFileSize(Context context, Uri uri, long def) { + if ("file".equals(uri.getScheme())) { + long size = new File(uri.getPath()).length(); + if (size == 0) { + size = def; + } + return size; + } + long size = def; try { Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null); @@ -261,6 +263,44 @@ public class FileHelper { } } + /** + * Deletes data at a URI securely by overwriting it with random data + * before deleting it. This method is fail-fast - if we can't securely + * delete the file, we don't delete it at all. + */ + public static int deleteFileSecurely(Context context, Uri uri) + throws IOException { + + ContentResolver resolver = context.getContentResolver(); + long lengthLeft = FileHelper.getFileSize(context, uri); + + if (lengthLeft == -1) { + throw new IOException("Error opening file!"); + } + + SecureRandom random = new SecureRandom(); + byte[] randomData = new byte[1024]; + + OutputStream out = resolver.openOutputStream(uri, "w"); + if (out == null) { + throw new IOException("Error opening file!"); + } + out = new BufferedOutputStream(out); + while (lengthLeft > 0) { + random.nextBytes(randomData); + out.write(randomData, 0, lengthLeft > randomData.length ? randomData.length : (int) lengthLeft); + lengthLeft -= randomData.length; + } + out.close(); + + if ("file".equals(uri.getScheme())) { + return new File(uri.getPath()).delete() ? 1 : 0; + } else { + return resolver.delete(uri, null, null); + } + + } + /** Checks if external storage is mounted if file is located on external storage. */ public static boolean isStorageMounted(String file) { if (file.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { -- cgit v1.2.3 From b5b197a9c4edde87cc36d61b13058c58520bafb0 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 15 Nov 2015 20:54:29 +0100 Subject: decryptlist: fix iterators --- .../keychain/ui/DecryptListFragment.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 000de6e40..8adaa0670 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import android.Manifest; @@ -676,11 +677,13 @@ public class DecryptListFragment if (permissionWasGranted) { // permission granted -> retry all cancelled file uris - for (Uri uri : mCancelledInputUris) { + Iterator it = mCancelledInputUris.iterator(); + while (it.hasNext()) { + Uri uri = it.next(); if ( ! "file".equals(uri.getScheme())) { continue; } - mCancelledInputUris.remove(uri); + it.remove(); mPendingInputUris.add(uri); mAdapter.setCancelled(uri, false); } @@ -688,12 +691,17 @@ public class DecryptListFragment } else { // permission denied -> cancel current, and all pending file uris + mCancelledInputUris.add(mCurrentInputUri); + mAdapter.setCancelled(mCurrentInputUri, true); + mCurrentInputUri = null; - for (final Uri uri : mPendingInputUris) { + Iterator it = mPendingInputUris.iterator(); + while (it.hasNext()) { + Uri uri = it.next(); if ( ! "file".equals(uri.getScheme())) { continue; } - mPendingInputUris.remove(uri); + it.remove(); mCancelledInputUris.add(uri); mAdapter.setCancelled(uri, true); } -- cgit v1.2.3 From 89d1b6e4dcd175872fabc2f5ac4c87adb3f3faf5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 16 Nov 2015 00:05:02 +0100 Subject: fix unit tests (IllegalStateException) --- .../keychain/pgp/PgpEncryptDecryptTest.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index d3c3f1df5..47c7a20ea 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -556,7 +556,7 @@ public class PgpEncryptDecryptTest { } @Test - public void testAsymmetricMultiSubkeyEncrypt() throws Exception { + public void testMultiSubkeyEncryptSkipStripOrBadFlag() throws Exception { String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true); @@ -610,7 +610,8 @@ public class PgpEncryptDecryptTest { { // strip first encrypted subkey, decryption should skip it - SaveKeyringParcel parcel = new SaveKeyringParcel(mStaticRing1.getMasterKeyId(), mStaticRing1.getFingerprint()); + SaveKeyringParcel parcel = + new SaveKeyringParcel(mStaticRing1.getMasterKeyId(), mStaticRing1.getFingerprint()); parcel.mChangeSubKeys.add(new SubkeyChange(encKeyId1, true, false)); UncachedKeyRing modified = PgpKeyOperationTest.applyModificationWithChecks(parcel, mStaticRing1, new ArrayList(), new ArrayList(), @@ -631,8 +632,9 @@ public class PgpEncryptDecryptTest { { // change flags of second encrypted subkey, decryption should skip it - SaveKeyringParcel parcel = new SaveKeyringParcel(mStaticRing1.getMasterKeyId(), mStaticRing1.getFingerprint()); - parcel.mChangeSubKeys.add(new SubkeyChange(encKeyId1, PGPKeyFlags.CAN_CERTIFY, null)); + SaveKeyringParcel parcel = + new SaveKeyringParcel(mStaticRing1.getMasterKeyId(), mStaticRing1.getFingerprint()); + parcel.mChangeSubKeys.add(new SubkeyChange(encKeyId1, KeyFlags.CERTIFY_OTHER, null)); UncachedKeyRing modified = PgpKeyOperationTest.applyModificationWithChecks(parcel, mStaticRing1, new ArrayList(), new ArrayList(), new CryptoInputParcel(new Date(), mKeyPhrase1)); @@ -650,6 +652,13 @@ public class PgpEncryptDecryptTest { result.getLog().containsType(LogType.MSG_DC_ASKIP_BAD_FLAGS)); } + } + + @Test + public void testMultiSubkeyEncryptSkipRevoked() throws Exception { + + String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true); + { // revoke first encryption subkey of keyring in database SaveKeyringParcel parcel = new SaveKeyringParcel(mStaticRing1.getMasterKeyId(), mStaticRing1.getFingerprint()); parcel.mRevokeSubKeys.add(KeyringTestingHelper.getSubkeyId(mStaticRing1, 2)); @@ -679,7 +688,7 @@ public class PgpEncryptDecryptTest { data, out); Assert.assertTrue("encryption must succeed", result.success()); - ciphertext = out.toByteArray(); + byte[] ciphertext = out.toByteArray(); Iterator packets = KeyringTestingHelper.parseKeyring(ciphertext); -- cgit v1.2.3 From ba7deca7d86a0fec582937599f2a7ec2a85bb52f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 16 Nov 2015 00:12:29 +0100 Subject: travis: add --stacktrace parameter --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 74ea7a58d..145d87862 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,4 +35,4 @@ android: script: # - ./gradlew connectedAndroidTest - - ./gradlew testDebug jacocoTestReport coveralls + - ./gradlew --stacktrace testDebug jacocoTestReport coveralls -- cgit v1.2.3