From 00e97586b06e5e61f6639b75423f9ec3edba47a0 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 14 Nov 2015 03:24:07 +0100 Subject: inline subkey editing (wip commit) --- .../keychain/ui/EditKeyFragment.java | 3 +- .../keychain/ui/ViewKeyAdvSubkeysFragment.java | 272 ++++++++++++++++++++- .../keychain/ui/ViewKeyAdvUserIdsFragment.java | 4 +- .../keychain/ui/adapter/SubkeysAdapter.java | 29 ++- 4 files changed, 294 insertions(+), 14 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index fa0edec48..969a84f14 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -231,7 +231,8 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment { public static final String ARG_DATA_URI = "data_uri"; + public static final int LOADER_ID_SUBKEYS = 0; private ListView mSubkeysList; + private ListView mSubkeysAddedList; + private View mSubkeysAddedLayout; + private ViewAnimator mSubkeyAddFabLayout; + private SubkeysAdapter mSubkeysAdapter; + private SubkeysAddedAdapter mSubkeysAddedAdapter; private Uri mDataUriSubkeys; + private boolean mHasSecret; + private SaveKeyringParcel mEditModeSaveKeyringParcel; + /** * Creates new instance of this fragment */ @@ -64,6 +93,36 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements mSubkeysList = (ListView) view.findViewById(R.id.keys); + mSubkeysList = (ListView) view.findViewById(R.id.view_key_user_ids); + mSubkeysAddedList = (ListView) view.findViewById(R.id.view_key_user_ids_added); + mSubkeysAddedLayout = view.findViewById(R.id.view_key_user_ids_add_layout); + + mSubkeysList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + editSubkey(position); + } + }); + + View footer = new View(getActivity()); + int spacing = (int) android.util.TypedValue.applyDimension( + android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics() + ); + android.widget.AbsListView.LayoutParams params = new android.widget.AbsListView.LayoutParams( + android.widget.AbsListView.LayoutParams.MATCH_PARENT, + spacing + ); + footer.setLayoutParams(params); + mSubkeysAddedList.addFooterView(footer, null, false); + + mSubkeyAddFabLayout = (ViewAnimator) view.findViewById(R.id.view_key_subkey_fab_layout); + view.findViewById(R.id.view_key_subkey_fab).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + addSubkey(); + } + }); + return root; } @@ -90,7 +149,7 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. - getLoaderManager().initLoader(0, null, this); + getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, this); } public Loader onCreateLoader(int id, Bundle args) { @@ -122,4 +181,215 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements mSubkeysAdapter.swapCursor(null); } + private void enterEditMode() { + FragmentActivity activity = getActivity(); + activity.startActionMode(new Callback() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + + mEditModeSaveKeyringParcel = new SaveKeyringParcel(0L, new byte[0]); + + mSubkeysAddedAdapter = + new SubkeysAddedAdapter(getActivity(), mEditModeSaveKeyringParcel.mAddSubKeys, false); + mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter); + mSubkeysAddedLayout.setVisibility(View.VISIBLE); + mSubkeyAddFabLayout.setDisplayedChild(1); + + mSubkeysAdapter.setEditMode(mEditModeSaveKeyringParcel); + getLoaderManager().restartLoader(LOADER_ID_SUBKEYS, null, ViewKeyAdvSubkeysFragment.this); + + mode.setTitle(R.string.title_edit_subkeys); + mode.getMenuInflater().inflate(R.menu.action_edit_uids, menu); + + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + mode.finish(); + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + mEditModeSaveKeyringParcel = null; + mSubkeysAdapter.setEditMode(null); + mSubkeysAddedLayout.setVisibility(View.GONE); + mSubkeyAddFabLayout.setDisplayedChild(0); + getLoaderManager().restartLoader(0, null, ViewKeyAdvSubkeysFragment.this); + } + }); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_edit_subkeys: + enterEditMode(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + private void addSubkey() { + boolean willBeMasterKey; + if (mSubkeysAdapter != null) { + willBeMasterKey = mSubkeysAdapter.getCount() == 0 && mSubkeysAddedAdapter.getCount() == 0; + } else { + willBeMasterKey = mSubkeysAddedAdapter.getCount() == 0; + } + + AddSubkeyDialogFragment addSubkeyDialogFragment = + AddSubkeyDialogFragment.newInstance(willBeMasterKey); + addSubkeyDialogFragment + .setOnAlgorithmSelectedListener( + new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() { + @Override + public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey) { + mSubkeysAddedAdapter.add(newSubkey); + } + } + ); + addSubkeyDialogFragment.show(getActivity().getSupportFragmentManager(), "addSubkeyDialog"); + } + + private void editSubkey(final int position) { + final long keyId = mSubkeysAdapter.getKeyId(position); + + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case EditSubkeyDialogFragment.MESSAGE_CHANGE_EXPIRY: + editSubkeyExpiry(position); + break; + case EditSubkeyDialogFragment.MESSAGE_REVOKE: + // toggle + if (mEditModeSaveKeyringParcel.mRevokeSubKeys.contains(keyId)) { + mEditModeSaveKeyringParcel.mRevokeSubKeys.remove(keyId); + } else { + mEditModeSaveKeyringParcel.mRevokeSubKeys.add(keyId); + } + break; + case EditSubkeyDialogFragment.MESSAGE_STRIP: { + SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); + if (secretKeyType == SecretKeyType.GNU_DUMMY) { + // Key is already stripped; this is a no-op. + break; + } + + SubkeyChange change = mEditModeSaveKeyringParcel.getSubkeyChange(keyId); + if (change == null) { + mEditModeSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, false)); + break; + } + // toggle + change.mDummyStrip = !change.mDummyStrip; + if (change.mDummyStrip && change.mMoveKeyToCard) { + // User had chosen to divert key, but now wants to strip it instead. + change.mMoveKeyToCard = false; + } + break; + } + case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_CARD: { + // TODO: enable later when Admin PIN handling is resolved + Notify.create(getActivity(), + "This feature will be available in an upcoming OpenKeychain version.", + Notify.Style.WARN).show(); + break; + +// Activity activity = EditKeyFragment.this.getActivity(); +// SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); +// if (secretKeyType == SecretKeyType.DIVERT_TO_CARD || +// secretKeyType == SecretKeyType.GNU_DUMMY) { +// Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR) +// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +// break; +// } +// int algorithm = mSubkeysAdapter.getAlgorithm(position); +// // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN +// if (algorithm != 1 && algorithm != 2 && algorithm != 3) { +// Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR) +// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +// break; +// } +// if (mSubkeysAdapter.getKeySize(position) != 2048) { +// Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR) +// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +// break; +// } +// +// +// SubkeyChange change; +// change = mSaveKeyringParcel.getSubkeyChange(keyId); +// if (change == null) { +// mSaveKeyringParcel.mChangeSubKeys.add( +// new SubkeyChange(keyId, false, true) +// ); +// break; +// } +// // toggle +// change.mMoveKeyToCard = !change.mMoveKeyToCard; +// if (change.mMoveKeyToCard && change.mDummyStrip) { +// // User had chosen to strip key, but now wants to divert it. +// change.mDummyStrip = false; +// } +// break; + } + } + getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); + } + }; + + // Create a new Messenger for the communication back + final Messenger messenger = new Messenger(returnHandler); + + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + EditSubkeyDialogFragment dialogFragment = + EditSubkeyDialogFragment.newInstance(messenger); + + dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyDialog"); + } + }); + } + + private void editSubkeyExpiry(final int position) { + final long keyId = mSubkeysAdapter.getKeyId(position); + final Long creationDate = mSubkeysAdapter.getCreationDate(position); + final Long expiryDate = mSubkeysAdapter.getExpiryDate(position); + + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY: + mEditModeSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry = + (Long) message.getData().getSerializable( + EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY); + break; + } + getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); + } + }; + + // Create a new Messenger for the communication back + final Messenger messenger = new Messenger(returnHandler); + + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + EditSubkeyExpiryDialogFragment dialogFragment = + EditSubkeyExpiryDialogFragment.newInstance(messenger, creationDate, expiryDate); + + dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyExpiryDialog"); + } + }); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java index e98970754..c8e892276 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java @@ -101,8 +101,8 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements footer.setLayoutParams(params); mUserIdsAddedList.addFooterView(footer, null, false); - mUserIdAddFabLayout = (ViewAnimator) view.findViewById(R.id.view_key_user_id_fab_layout); - view.findViewById(R.id.view_key_user_id_fab).setOnClickListener(new View.OnClickListener() { + mUserIdAddFabLayout = (ViewAnimator) view.findViewById(R.id.view_key_subkey_fab_layout); + view.findViewById(R.id.view_key_subkey_fab).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { addUserId(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index 24f5f04a1..84608f2dc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -22,6 +22,7 @@ import android.content.res.ColorStateList; import android.database.Cursor; import android.graphics.PorterDuff; import android.graphics.Typeface; +import android.support.annotation.Nullable; import android.support.v4.widget.CursorAdapter; import android.text.Spannable; import android.text.SpannableString; @@ -49,7 +50,7 @@ public class SubkeysAdapter extends CursorAdapter { private LayoutInflater mInflater; private SaveKeyringParcel mSaveKeyringParcel; - private boolean hasAnySecret; + private boolean mHasAnySecret; private ColorStateList mDefaultTextColor; public static final String[] SUBKEYS_PROJECTION = new String[]{ @@ -85,16 +86,10 @@ public class SubkeysAdapter extends CursorAdapter { private static final int INDEX_EXPIRY = 13; private static final int INDEX_FINGERPRINT = 14; - public SubkeysAdapter(Context context, Cursor c, int flags, - SaveKeyringParcel saveKeyringParcel) { + public SubkeysAdapter(Context context, Cursor c, int flags) { super(context, c, flags); mInflater = LayoutInflater.from(context); - mSaveKeyringParcel = saveKeyringParcel; - } - - public SubkeysAdapter(Context context, Cursor c, int flags) { - this(context, c, flags, null); } public long getKeyId(int position) { @@ -133,12 +128,12 @@ public class SubkeysAdapter extends CursorAdapter { @Override public Cursor swapCursor(Cursor newCursor) { - hasAnySecret = false; + mHasAnySecret = false; if (newCursor != null && newCursor.moveToFirst()) { do { SecretKeyType hasSecret = SecretKeyType.fromNum(newCursor.getInt(INDEX_HAS_SECRET)); if (hasSecret.isUsable()) { - hasAnySecret = true; + mHasAnySecret = true; break; } } while (newCursor.moveToNext()); @@ -354,4 +349,18 @@ public class SubkeysAdapter extends CursorAdapter { } } + /** Set this adapter into edit mode. This mode displays additional info for + * each item from a supplied SaveKeyringParcel reference. + * + * Note that it is up to the caller to reload the underlying cursor after + * updating the SaveKeyringParcel! + * + * @see SaveKeyringParcel + * + * @param saveKeyringParcel The parcel to get info from, or null to leave edit mode. + */ + public void setEditMode(@Nullable SaveKeyringParcel saveKeyringParcel) { + mSaveKeyringParcel = saveKeyringParcel; + } + } -- cgit v1.2.3