diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java index e8caa8c4c..8459446d2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java @@ -360,6 +360,11 @@ public class KeychainDatabase { db.execSQL("ALTER TABLE keys ADD COLUMN validFrom INTEGER NOT NULL DEFAULT 0;"); db.execSQL("UPDATE keys SET validFrom = creation"); db.setTransactionSuccessful(); + } catch (SQLiteException e) { + // column probably already existed, nvm this + if (!Constants.DEBUG) { + throw e; + } } finally { db.endTransaction(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 1ae2d9690..bb13973cf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -27,6 +27,7 @@ import android.os.Parcelable; import android.support.annotation.Nullable; import com.google.auto.value.AutoValue; +import org.bouncycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.util.Passphrase; @@ -224,6 +225,18 @@ public abstract class SaveKeyringParcel implements Parcelable { return autoBuild(); } + + public boolean hasModificationsForSubkey(long keyId) { + return revokeSubKeys.contains(keyId) || getSubkeyChange(keyId) != null; + } + + public void removeModificationsForSubkey(long keyId) { + revokeSubKeys.remove(keyId); + SubkeyChange subkeyChange = getSubkeyChange(keyId); + if (subkeyChange != null) { + changeSubKeys.remove(subkeyChange); + } + } } // performance gain for using Parcelable here would probably be negligible, @@ -243,6 +256,22 @@ public abstract class SaveKeyringParcel implements Parcelable { Long expiry) { return new AutoValue_SaveKeyringParcel_SubkeyAdd(algorithm, keySize, curve, flags, expiry); } + + public boolean canCertify() { + return (getFlags() & KeyFlags.CERTIFY_OTHER) > 0; + } + + public boolean canSign() { + return (getFlags() & KeyFlags.SIGN_DATA) > 0; + } + + public boolean canEncrypt() { + return ((getFlags() & KeyFlags.ENCRYPT_COMMS) > 0) || ((getFlags() & KeyFlags.ENCRYPT_STORAGE) > 0); + } + + public boolean canAuthenticate() { + return (getFlags() & KeyFlags.AUTHENTICATION) > 0; + } } @AutoValue 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 0bbabeab6..f0c0ba3d3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -117,7 +117,7 @@ public class EditKeyFragment extends Fragment { mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSkpBuilder.getMutableAddUserIds(), true); mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); - mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSkpBuilder.getMutableAddSubKeys(), true); + mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSkpBuilder.getMutableAddSubKeys()); mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SubKeyItem.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SubKeyItem.java new file mode 100644 index 000000000..4f737c377 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SubKeyItem.java @@ -0,0 +1,268 @@ +package org.sufficientlysecure.keychain.ui; + + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.Typeface; +import android.support.annotation.StringRes; +import android.text.format.DateFormat; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageView; +import android.widget.TextView; + +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; +import eu.davidea.flexibleadapter.items.IFlexible; +import eu.davidea.viewholders.FlexibleViewHolder; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Builder; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; +import org.sufficientlysecure.keychain.ui.ViewKeyAdvSubkeysFragment.SubkeyEditViewModel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + + +public class SubKeyItem extends AbstractFlexibleItem { + final SubKey subkeyInfo; + private final SubkeyEditViewModel viewModel; + + SubKeyItem(SubKey subkeyInfo, SubkeyEditViewModel viewModel) { + this.subkeyInfo = subkeyInfo; + this.viewModel = viewModel; + } + + @Override + public boolean equals(Object o) { + return o instanceof SubKeyItem && ((SubKeyItem) o).subkeyInfo.key_id() == subkeyInfo.key_id(); + } + + @Override + public int hashCode() { + long key_id = subkeyInfo.key_id(); + return (int) (key_id ^ (key_id >>> 32)); + } + + @Override + public int getLayoutRes() { + return R.layout.view_key_adv_subkey_item; + } + + @Override + public SubkeyViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new SubkeyViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, SubkeyViewHolder holder, int position, + List payloads) { + holder.bind(subkeyInfo); + holder.bindSubkeyAction(subkeyInfo, viewModel.skpBuilder); + } + + @Override + public int getItemViewType() { + return ViewKeyAdvSubkeysFragment.SUBKEY_TYPE_DETAIL; + } + + public static class SubkeyViewHolder extends FlexibleViewHolder { + final TextView vKeyId; + final TextView vKeyDetails; + final TextView vKeyStatus; + final ImageView vCertifyIcon; + final ImageView vSignIcon; + final ImageView vEncryptIcon; + final ImageView vAuthenticateIcon; + final View vActionLayout; + final TextView vActionText; + final ImageView vActionCancel; + + public SubkeyViewHolder(View itemView, FlexibleAdapter adapter) { + super(itemView, adapter); + + vKeyId = itemView.findViewById(R.id.subkey_item_key_id); + vKeyDetails = itemView.findViewById(R.id.subkey_item_details); + vKeyStatus = itemView.findViewById(R.id.subkey_item_status); + vCertifyIcon = itemView.findViewById(R.id.subkey_item_ic_certify); + vSignIcon = itemView.findViewById(R.id.subkey_item_ic_sign); + vEncryptIcon = itemView.findViewById(R.id.subkey_item_ic_encrypt); + vAuthenticateIcon = itemView.findViewById(R.id.subkey_item_ic_authenticate); + vActionLayout = itemView.findViewById(R.id.layout_subkey_action); + vActionText = itemView.findViewById(R.id.text_subkey_action); + vActionCancel = itemView.findViewById(R.id.button_subkey_action_cancel); + } + + void bind(SubKey subkeyInfo) { + bindKeyId(subkeyInfo.key_id(), subkeyInfo.rank() == 0); + bindKeyDetails(subkeyInfo.algorithm(), subkeyInfo.key_size(), subkeyInfo.key_curve_oid(), subkeyInfo.has_secret()); + bindKeyFlags(subkeyInfo.can_certify(), subkeyInfo.can_sign(), subkeyInfo.can_encrypt(), subkeyInfo.can_authenticate()); + + Date validFrom = new Date(subkeyInfo.validFrom() * 1000); + Date expiryDate = subkeyInfo.expires() ? new Date(subkeyInfo.expiry() * 1000) : null; + bindKeyStatus(validFrom, expiryDate, subkeyInfo.is_revoked(), subkeyInfo.is_secure()); + } + + public void bindKeyId(Long keyId, boolean isMasterKey) { + if (keyId == null) { + vKeyId.setText(R.string.edit_key_new_subkey); + } else { + vKeyId.setText(KeyFormattingUtils.beautifyKeyId(keyId)); + } + vKeyId.setTypeface(null, isMasterKey ? Typeface.BOLD : Typeface.NORMAL); + } + + public void bindKeyStatus(Date validFrom, Date expiryDate, boolean isRevoked, boolean isSecure) { + Context context = itemView.getContext(); + Date now = new Date(); + + boolean isNotYetValid = validFrom != null && validFrom.after(now); + boolean isExpired = expiryDate != null && expiryDate.before(now); + if (isNotYetValid) { + Calendar validFromCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + validFromCal.setTime(validFrom); + // convert from UTC to time zone of device + validFromCal.setTimeZone(TimeZone.getDefault()); + + vKeyStatus.setText(context.getString(R.string.label_valid_from) + ": " + + DateFormat.getDateFormat(context).format(validFromCal.getTime())); + } else if (isRevoked) { + vKeyStatus.setText(R.string.label_revoked); + } else if (!isSecure) { + vKeyStatus.setText(R.string.label_insecure); + } else if (expiryDate != null) { + Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + expiryCal.setTime(expiryDate); + // convert from UTC to time zone of device + expiryCal.setTimeZone(TimeZone.getDefault()); + + vKeyStatus.setText(context.getString(R.string.label_expiry) + ": " + + DateFormat.getDateFormat(context).format(expiryCal.getTime())); + } else { + vKeyStatus.setText(""); + } + + boolean isValid = !isRevoked && !isExpired && !isNotYetValid && isSecure; + bindValidityStatus(isValid); + } + + private void bindValidityStatus(boolean isValid) { + if (!isValid) { + int key_flag_gray = itemView.getResources().getColor(R.color.key_flag_gray); + vCertifyIcon.setColorFilter(key_flag_gray, PorterDuff.Mode.SRC_IN); + vSignIcon.setColorFilter(key_flag_gray, PorterDuff.Mode.SRC_IN); + vEncryptIcon.setColorFilter(key_flag_gray, PorterDuff.Mode.SRC_IN); + vAuthenticateIcon.setColorFilter(key_flag_gray, PorterDuff.Mode.SRC_IN); + } else { + vCertifyIcon.clearColorFilter(); + vSignIcon.clearColorFilter(); + vEncryptIcon.clearColorFilter(); + vAuthenticateIcon.clearColorFilter(); + } + + vKeyId.setEnabled(isValid); + vKeyDetails.setEnabled(isValid); + vKeyStatus.setEnabled(isValid); + } + + public void bindKeyDetails(Algorithm algorithm, Integer keySize, Curve curveOid, SecretKeyType secretKeyType) { + Context context = itemView.getContext(); + + String algorithmStr = KeyFormattingUtils.getAlgorithmInfo(context, algorithm, keySize, curveOid); + bindKeyDetails(context, algorithmStr, secretKeyType); + } + + void bindKeyDetails(int algorithm, Integer keySize, String curveOid, SecretKeyType secretKeyType) { + Context context = itemView.getContext(); + + String algorithmStr = KeyFormattingUtils.getAlgorithmInfo(context, algorithm, keySize, curveOid); + bindKeyDetails(context, algorithmStr, secretKeyType); + } + + private void bindKeyDetails(Context context, String algorithmStr, SecretKeyType secretKeyType) { + switch (secretKeyType) { + case GNU_DUMMY: + algorithmStr += ", " + context.getString(R.string.key_stripped); + break; + case DIVERT_TO_CARD: + algorithmStr += ", " + context.getString(R.string.key_divert); + break; + } + vKeyDetails.setText(algorithmStr); + } + + private void bindSubkeyAction(SubKey subkeyInfo, Builder saveKeyringParcelBuilder) { + if (saveKeyringParcelBuilder == null) { + itemView.setClickable(false); + vActionLayout.setVisibility(View.GONE); + return; + } + boolean isRevokeAction = (saveKeyringParcelBuilder.getMutableRevokeSubKeys().contains(subkeyInfo.key_id())); + SubkeyChange change = saveKeyringParcelBuilder.getSubkeyChange(subkeyInfo.key_id()); + boolean hasAction = isRevokeAction || change != null; + if (!hasAction) { + itemView.setClickable(true); + vActionLayout.setVisibility(View.GONE); + return; + } + + OnClickListener onClickRemoveModificationListener = v -> { + saveKeyringParcelBuilder.removeModificationsForSubkey(subkeyInfo.key_id()); + mAdapter.notifyItemChanged(getAdapterPosition()); + }; + + if (isRevokeAction) { + bindSubkeyAction(R.string.subkey_action_revoke, onClickRemoveModificationListener); + return; + } + + if (change.getDummyStrip()) { + bindSubkeyAction(R.string.subkey_action_strip, onClickRemoveModificationListener); + return; + } + Long expiry = change.getExpiry(); + if (expiry != null) { + if (expiry == 0L) { + bindSubkeyAction(R.string.subkey_action_expiry_never, onClickRemoveModificationListener); + } else { + String expiryString = itemView.getContext().getString(R.string.subkey_action_expiry_date, + DateFormat.getDateFormat(itemView.getContext()).format(new Date(expiry * 1000))); + bindSubkeyAction(expiryString, onClickRemoveModificationListener); + } + return; + } + + throw new UnsupportedOperationException(); + } + + void bindSubkeyAction(String actionText, OnClickListener onClickListener) { + vActionText.setText(actionText); + bindSubkeyAction(onClickListener); + } + + public void bindSubkeyAction(@StringRes int actionTextRes, OnClickListener onClickListener) { + vActionText.setText(actionTextRes); + bindSubkeyAction(onClickListener); + } + + private void bindSubkeyAction(OnClickListener onClickListener) { + itemView.setClickable(false); + vActionLayout.setVisibility(View.VISIBLE); + vActionCancel.setOnClickListener(onClickListener); + } + + public void bindKeyFlags(boolean canCertify, boolean canSign, boolean canEncrypt, boolean canAuthenticate) { + vCertifyIcon.setVisibility(canCertify ? View.VISIBLE : View.GONE); + vSignIcon.setVisibility(canSign ? View.VISIBLE : View.GONE); + vEncryptIcon.setVisibility(canEncrypt ? View.VISIBLE : View.GONE); + vAuthenticateIcon.setVisibility(canAuthenticate ? View.VISIBLE : View.GONE); + } + } +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java index c1984e888..170343704 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java @@ -18,8 +18,10 @@ package org.sufficientlysecure.keychain.ui; +import java.util.ArrayList; import java.util.List; +import android.arch.lifecycle.ViewModel; import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.os.Bundle; @@ -29,15 +31,19 @@ import android.os.Messenger; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.ListView; import android.widget.ViewAnimator; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener; +import eu.davidea.flexibleadapter.items.IFlexible; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.model.SubKey; @@ -45,52 +51,38 @@ import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Builder; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel; -import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; -import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; +import org.sufficientlysecure.keychain.ui.adapter.SubkeyAddedItem; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment; +import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; public class ViewKeyAdvSubkeysFragment extends Fragment { - private ListView mSubkeysList; - private ListView mSubkeysAddedList; - private View mSubkeysAddedLayout; - private ViewAnimator mSubkeyAddFabLayout; + public static final int SUBKEY_TYPE_DETAIL = 1; + public static final int SUBKEY_TYPE_ADDED = 2; - private SubkeysAdapter mSubkeysAdapter; - private SubkeysAddedAdapter mSubkeysAddedAdapter; + private RecyclerView subkeysList; + private ViewAnimator subkeyAddFabLayout; + + private FlexibleAdapter subkeysAdapter; private CryptoOperationHelper mEditKeyHelper; - - private SaveKeyringParcel.Builder mEditModeSkpBuilder; - private UnifiedKeyInfo unifiedKeyInfo; + private SubkeyEditViewModel subkeyEditViewModel; @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.view_key_adv_subkeys_fragment, viewGroup, false); - mSubkeysList = view.findViewById(R.id.view_key_subkeys); - mSubkeysAddedList = view.findViewById(R.id.view_key_subkeys_added); - mSubkeysAddedLayout = view.findViewById(R.id.view_key_subkeys_add_layout); + subkeysList = view.findViewById(R.id.view_key_subkeys); + subkeysList.setLayoutManager(new LinearLayoutManager(requireContext())); + subkeysList.addItemDecoration(new DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL, false)); - mSubkeysList.setOnItemClickListener((parent, view1, position, 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 = view.findViewById(R.id.view_key_subkey_fab_layout); + subkeyAddFabLayout = view.findViewById(R.id.view_key_subkey_fab_layout); view.findViewById(R.id.view_key_subkey_fab).setOnClickListener(v -> addSubkey()); setHasOptionsMenu(true); @@ -102,26 +94,32 @@ public class ViewKeyAdvSubkeysFragment extends Fragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - // Create an empty adapter we will use to display the loaded data. - mSubkeysAdapter = new SubkeysAdapter(requireContext()); - mSubkeysList.setAdapter(mSubkeysAdapter); + subkeysAdapter = new FlexibleAdapter<>(null, null, true); + subkeysAdapter.addListener((OnItemClickListener) (view, position) -> editSubkey(position)); + subkeysList.setAdapter(subkeysAdapter); ViewKeyAdvViewModel viewModel = ViewModelProviders.of(requireActivity()).get(ViewKeyAdvViewModel.class); - viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadFinished); + viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyId); viewModel.getSubkeyLiveData(requireContext()).observe(this, this::onLoadSubKeys); + + subkeyEditViewModel = ViewModelProviders.of(this).get(SubkeyEditViewModel.class); } - public void onLoadFinished(UnifiedKeyInfo unifiedKeyInfo) { - // Avoid NullPointerExceptions, if we get an empty result set. - if (unifiedKeyInfo == null) { - return; - } + public static class SubkeyEditViewModel extends ViewModel { + public Builder skpBuilder; + UnifiedKeyInfo unifiedKeyInfo; + } - this.unifiedKeyInfo = unifiedKeyInfo; + public void onLoadUnifiedKeyId(UnifiedKeyInfo unifiedKeyInfo) { + subkeyEditViewModel.unifiedKeyInfo = unifiedKeyInfo; } private void onLoadSubKeys(List subKeys) { - mSubkeysAdapter.setData(subKeys); + ArrayList subKeyItems = new ArrayList<>(subKeys.size()); + for (SubKey subKey : subKeys) { + subKeyItems.add(new SubKeyItem(subKey, subkeyEditViewModel)); + } + subkeysAdapter.updateDataSet(subKeyItems); } @Override @@ -152,16 +150,10 @@ public class ViewKeyAdvSubkeysFragment extends Fragment { activity.startActionMode(new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - - mEditModeSkpBuilder = SaveKeyringParcel.buildChangeKeyringParcel(unifiedKeyInfo.master_key_id(), unifiedKeyInfo.fingerprint()); - - mSubkeysAddedAdapter = new SubkeysAddedAdapter( - getActivity(), mEditModeSkpBuilder.getMutableAddSubKeys(), false); - mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter); - mSubkeysAddedLayout.setVisibility(View.VISIBLE); - mSubkeyAddFabLayout.setDisplayedChild(1); - - mSubkeysAdapter.setEditMode(mEditModeSkpBuilder); + subkeyAddFabLayout.setDisplayedChild(1); + subkeyEditViewModel.skpBuilder = SaveKeyringParcel.buildChangeKeyringParcel( + subkeyEditViewModel.unifiedKeyInfo.master_key_id(), subkeyEditViewModel.unifiedKeyInfo.fingerprint()); + subkeysAdapter.notifyDataSetChanged(); mode.setTitle(R.string.title_edit_subkeys); mode.getMenuInflater().inflate(R.menu.action_edit_uids, menu); @@ -182,63 +174,60 @@ public class ViewKeyAdvSubkeysFragment extends Fragment { @Override public void onDestroyActionMode(ActionMode mode) { - mEditModeSkpBuilder = null; - mSubkeysAdapter.setEditMode(null); - mSubkeysAddedLayout.setVisibility(View.GONE); - mSubkeyAddFabLayout.setDisplayedChild(0); + subkeyEditViewModel.skpBuilder = null; + subkeysAdapter.removeItemsOfType(2); + subkeyAddFabLayout.setDisplayedChild(0); + subkeysAdapter.notifyDataSetChanged(); } }); } private void addSubkey() { - boolean willBeMasterKey; - if (mSubkeysAdapter != null) { - willBeMasterKey = mSubkeysAdapter.getCount() == 0 && mSubkeysAddedAdapter.getCount() == 0; - } else { - willBeMasterKey = mSubkeysAddedAdapter.getCount() == 0; - } + boolean willBeMasterKey = subkeysAdapter.getItemCount() == 0; - AddSubkeyDialogFragment addSubkeyDialogFragment = - AddSubkeyDialogFragment.newInstance(willBeMasterKey); - addSubkeyDialogFragment - .setOnAlgorithmSelectedListener(newSubkey -> mSubkeysAddedAdapter.add(newSubkey)); + AddSubkeyDialogFragment addSubkeyDialogFragment = AddSubkeyDialogFragment.newInstance(willBeMasterKey); + addSubkeyDialogFragment.setOnAlgorithmSelectedListener(newSubkey -> { + subkeyEditViewModel.skpBuilder.addSubkeyAdd(newSubkey); + subkeysAdapter.addItem(new SubkeyAddedItem(newSubkey, subkeyEditViewModel)); + }); addSubkeyDialogFragment.show(requireFragmentManager(), "addSubkeyDialog"); } - private void editSubkey(final int position) { - final SubKey subKey = mSubkeysAdapter.getItem(position); + private boolean editSubkey(final int position) { + if (subkeyEditViewModel.skpBuilder == null) { + return false; + } + + IFlexible item = subkeysAdapter.getItem(position); + if (item instanceof SubKeyItem) { + editSubkey(position, ((SubKeyItem) item)); + } + + return false; + } + + private void editSubkey(int position, SubKeyItem item) { + if (subkeyEditViewModel.skpBuilder.hasModificationsForSubkey(item.subkeyInfo.key_id())) { + return; + } Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case EditSubkeyDialogFragment.MESSAGE_CHANGE_EXPIRY: - editSubkeyExpiry(position); + editSubkeyExpiry(item); break; case EditSubkeyDialogFragment.MESSAGE_REVOKE: - // toggle - if (mEditModeSkpBuilder.getMutableRevokeSubKeys().contains(subKey.key_id())) { - mEditModeSkpBuilder.removeRevokeSubkey(subKey.key_id()); - } else { - mEditModeSkpBuilder.addRevokeSubkey(subKey.key_id()); - } + SubKey subKey = item.subkeyInfo; + subkeyEditViewModel.skpBuilder.addRevokeSubkey(subKey.key_id()); break; case EditSubkeyDialogFragment.MESSAGE_STRIP: { - if (subKey.has_secret() == SecretKeyType.GNU_DUMMY) { - // Key is already stripped; this is a no-op. - break; - } - - SubkeyChange change = mEditModeSkpBuilder.getSubkeyChange(subKey.key_id()); - if (change == null || !change.getDummyStrip()) { - mEditModeSkpBuilder.addOrReplaceSubkeyChange(SubkeyChange.createStripChange(subKey.key_id())); - } else { - mEditModeSkpBuilder.removeSubkeyChange(change); - } + editSubkeyToggleStrip(item); break; } } - mSubkeysAdapter.notifyDataSetChanged(); + subkeysAdapter.notifyItemChanged(position); } }; @@ -246,15 +235,24 @@ public class ViewKeyAdvSubkeysFragment extends Fragment { final Messenger messenger = new Messenger(returnHandler); DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(() -> { - EditSubkeyDialogFragment dialogFragment = - EditSubkeyDialogFragment.newInstance(messenger); - + EditSubkeyDialogFragment dialogFragment = EditSubkeyDialogFragment.newInstance(messenger); dialogFragment.show(requireFragmentManager(), "editSubkeyDialog"); }); } - private void editSubkeyExpiry(final int position) { - SubKey subKey = mSubkeysAdapter.getItem(position); + private void editSubkeyToggleStrip(SubKeyItem item) { + SubKey subKey = item.subkeyInfo; + if (subKey.has_secret() == SecretKeyType.GNU_DUMMY) { + // Key is already stripped; this is a no-op. + return; + } + + subkeyEditViewModel.skpBuilder.addOrReplaceSubkeyChange(SubkeyChange.createStripChange(subKey.key_id())); + } + + private void editSubkeyExpiry(SubKeyItem item) { + SubKey subKey = item.subkeyInfo; + final long keyId = subKey.key_id(); final Long creationDate = subKey.creation(); final Long expiryDate = subKey.expiry(); @@ -266,11 +264,11 @@ public class ViewKeyAdvSubkeysFragment extends Fragment { case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY: Long expiry = (Long) message.getData().getSerializable( EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY); - mEditModeSkpBuilder.addOrReplaceSubkeyChange( + subkeyEditViewModel.skpBuilder.addOrReplaceSubkeyChange( SubkeyChange.createFlagsOrExpiryChange(keyId, null, expiry)); break; } - mSubkeysAdapter.notifyDataSetChanged(); + subkeysAdapter.notifyDataSetChanged(); } }; @@ -292,7 +290,7 @@ public class ViewKeyAdvSubkeysFragment extends Fragment { @Override public SaveKeyringParcel createOperationInput() { - return mEditModeSkpBuilder.build(); + return subkeyEditViewModel.skpBuilder.build(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeyAddedItem.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeyAddedItem.java new file mode 100644 index 000000000..c56c56c34 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeyAddedItem.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017 Schürmann & Breitmoser GbR + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.adapter; + + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +import android.content.Context; +import android.graphics.Typeface; +import android.text.format.DateFormat; +import android.view.View; + +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; +import eu.davidea.flexibleadapter.items.IFlexible; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; +import org.sufficientlysecure.keychain.ui.SubKeyItem.SubkeyViewHolder; +import org.sufficientlysecure.keychain.ui.ViewKeyAdvSubkeysFragment; +import org.sufficientlysecure.keychain.ui.ViewKeyAdvSubkeysFragment.SubkeyEditViewModel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + + +public class SubkeyAddedItem extends AbstractFlexibleItem { + private SubkeyAdd subkeyAdd; + private final SubkeyEditViewModel viewModel; + + public SubkeyAddedItem(SubkeyAdd newSubkey, SubkeyEditViewModel viewModel) { + this.subkeyAdd = newSubkey; + this.viewModel = viewModel; + } + + @Override + public boolean equals(Object o) { + return o instanceof SubkeyAddedItem && subkeyAdd == o; + } + + @Override + public int hashCode() { + return subkeyAdd.hashCode(); + } + + @Override + public int getLayoutRes() { + return R.layout.view_key_adv_subkey_item; + } + + @Override + public SubkeyViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new SubkeyViewHolder(view, adapter); + } + + @Override + public int getItemViewType() { + return ViewKeyAdvSubkeysFragment.SUBKEY_TYPE_ADDED; + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, SubkeyViewHolder holder, int position, + List payloads) { + Date expiry = subkeyAdd.getExpiry() != null && subkeyAdd.getExpiry() != 0L ? new Date(subkeyAdd.getExpiry() * 1000) : null; + + holder.bindKeyId(null, false); + holder.bindKeyDetails(subkeyAdd.getAlgorithm(), subkeyAdd.getKeySize(), subkeyAdd.getCurve(), SecretKeyType.PASSPHRASE); + holder.bindKeyStatus(null, expiry, false, true); + holder.bindKeyFlags(subkeyAdd.canCertify(), subkeyAdd.canSign(), subkeyAdd.canEncrypt(), subkeyAdd.canAuthenticate()); + holder.bindSubkeyAction(R.string.subkey_action_create, v -> { + viewModel.skpBuilder.getMutableAddSubKeys().remove(subkeyAdd); + adapter.removeItem(position); + }); + } + +} 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 deleted file mode 100644 index d353cfc8c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.adapter; - - -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.TimeZone; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.PorterDuff; -import android.graphics.Typeface; -import android.support.annotation.Nullable; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.format.DateFormat; -import android.text.style.StyleSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.model.SubKey; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; - -public class SubkeysAdapter extends BaseAdapter { - private final Context context; - private final LayoutInflater layoutInflater; - - private List data; - private SaveKeyringParcel.Builder mSkpBuilder; - - private ColorStateList mDefaultTextColor; - - public SubkeysAdapter(Context context) { - this.context = context; - layoutInflater = LayoutInflater.from(context); - } - - public void setData(List data) { - this.data = data; - notifyDataSetChanged(); - } - - @Override - public int getCount() { - return data != null ? data.size() : 0; - } - - @Override - public SubKey getItem(int position) { - return data.get(position); - } - - @Override - public long getItemId(int position) { - return data.get(position).key_id(); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View view; - if (convertView != null) { - view = convertView; - } else { - view = layoutInflater.inflate(R.layout.view_key_adv_subkey_item, parent, false); - } - - Date now = new Date(); - - if (mDefaultTextColor == null) { - TextView keyId = view.findViewById(R.id.subkey_item_key_id); - mDefaultTextColor = keyId.getTextColors(); - } - - TextView vKeyId = view.findViewById(R.id.subkey_item_key_id); - TextView vKeyDetails = view.findViewById(R.id.subkey_item_details); - TextView vKeyExpiry = view.findViewById(R.id.subkey_item_expiry); - ImageView vCertifyIcon = view.findViewById(R.id.subkey_item_ic_certify); - ImageView vSignIcon = view.findViewById(R.id.subkey_item_ic_sign); - ImageView vEncryptIcon = view.findViewById(R.id.subkey_item_ic_encrypt); - ImageView vAuthenticateIcon = view.findViewById(R.id.subkey_item_ic_authenticate); - ImageView vEditImage = view.findViewById(R.id.subkey_item_edit_image); - ImageView vStatus = view.findViewById(R.id.subkey_item_status); - - // not used: - ImageView deleteImage = view.findViewById(R.id.subkey_item_delete_button); - deleteImage.setVisibility(View.GONE); - - SubKey subKey = getItem(position); - - vKeyId.setText(KeyFormattingUtils.beautifyKeyId(subKey.key_id())); - - // may be set with additional "stripped" later on - SpannableStringBuilder algorithmStr = new SpannableStringBuilder(); - algorithmStr.append(KeyFormattingUtils.getAlgorithmInfo( - context, - subKey.algorithm(), - subKey.key_size(), - subKey.key_curve_oid() - )); - - SubkeyChange change = mSkpBuilder != null ? mSkpBuilder.getSubkeyChange(subKey.key_id()) : null; - if (change != null && (change.getDummyStrip() || change.getMoveKeyToSecurityToken())) { - if (change.getDummyStrip()) { - algorithmStr.append(", "); - final SpannableString boldStripped = new SpannableString( - context.getString(R.string.key_stripped) - ); - boldStripped.setSpan(new StyleSpan(Typeface.BOLD), 0, boldStripped.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - algorithmStr.append(boldStripped); - } - if (change.getMoveKeyToSecurityToken()) { - algorithmStr.append(", "); - final SpannableString boldDivert = new SpannableString( - context.getString(R.string.key_divert) - ); - boldDivert.setSpan(new StyleSpan(Typeface.BOLD), 0, boldDivert.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - algorithmStr.append(boldDivert); - } - } else { - switch (subKey.has_secret()) { - case GNU_DUMMY: - algorithmStr.append(", "); - algorithmStr.append(context.getString(R.string.key_stripped)); - break; - case DIVERT_TO_CARD: - algorithmStr.append(", "); - algorithmStr.append(context.getString(R.string.key_divert)); - break; - case PASSPHRASE_EMPTY: - algorithmStr.append(", "); - algorithmStr.append(context.getString(R.string.key_no_passphrase)); - break; - case UNAVAILABLE: - // don't show this on pub keys - //algorithmStr += ", " + context.getString(R.string.key_unavailable); - break; - } - } - vKeyDetails.setText(algorithmStr, TextView.BufferType.SPANNABLE); - - boolean isMasterKey = subKey.rank() == 0; - if (isMasterKey) { - vKeyId.setTypeface(null, Typeface.BOLD); - } else { - vKeyId.setTypeface(null, Typeface.NORMAL); - } - - // Set icons according to properties - vCertifyIcon.setVisibility(subKey.can_certify() ? View.VISIBLE : View.GONE); - vEncryptIcon.setVisibility(subKey.can_encrypt() ? View.VISIBLE : View.GONE); - vSignIcon.setVisibility(subKey.can_sign() ? View.VISIBLE : View.GONE); - vAuthenticateIcon.setVisibility(subKey.can_authenticate() ? View.VISIBLE : View.GONE); - - boolean isRevoked = subKey.is_revoked(); - - Date expiryDate = null; - if (subKey.expires()) { - expiryDate = new Date(subKey.expiry() * 1000); - } - Date validFrom = new Date(subKey.validFrom() * 1000); - - // for edit key - if (mSkpBuilder != null) { - boolean revokeThisSubkey = (mSkpBuilder.getMutableRevokeSubKeys().contains(subKey.key_id())); - - if (revokeThisSubkey) { - if (!isRevoked) { - isRevoked = true; - } - } - - SaveKeyringParcel.SubkeyChange subkeyChange = mSkpBuilder.getSubkeyChange(subKey.key_id()); - if (subkeyChange != null) { - if (subkeyChange.getExpiry() == null || subkeyChange.getExpiry() == 0L) { - expiryDate = null; - } else { - expiryDate = new Date(subkeyChange.getExpiry() * 1000); - } - } - - vEditImage.setVisibility(View.VISIBLE); - } else { - vEditImage.setVisibility(View.GONE); - } - - boolean isNotYetValid = validFrom.after(now); - boolean isExpired = expiryDate != null && expiryDate.before(now); - if (isNotYetValid) { - Calendar validFromCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - validFromCal.setTime(validFrom); - // convert from UTC to time zone of device - validFromCal.setTimeZone(TimeZone.getDefault()); - - vKeyExpiry.setText(context.getString(R.string.label_valid_from) + ": " - + DateFormat.getDateFormat(context).format(validFromCal.getTime())); - } else if (expiryDate != null) { - Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - expiryCal.setTime(expiryDate); - // convert from UTC to time zone of device - expiryCal.setTimeZone(TimeZone.getDefault()); - - vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": " - + DateFormat.getDateFormat(context).format(expiryCal.getTime())); - } else { - vKeyExpiry.setText(""); - } - - // if key is expired or revoked... - boolean isInvalid = isRevoked || isExpired || isNotYetValid || !subKey.is_secure(); - if (isInvalid) { - vStatus.setVisibility(View.VISIBLE); - - vCertifyIcon.setColorFilter( - context.getResources().getColor(R.color.key_flag_gray), - PorterDuff.Mode.SRC_IN); - vSignIcon.setColorFilter( - context.getResources().getColor(R.color.key_flag_gray), - PorterDuff.Mode.SRC_IN); - vEncryptIcon.setColorFilter( - context.getResources().getColor(R.color.key_flag_gray), - PorterDuff.Mode.SRC_IN); - vAuthenticateIcon.setColorFilter( - context.getResources().getColor(R.color.key_flag_gray), - PorterDuff.Mode.SRC_IN); - - if (isRevoked) { - vStatus.setImageResource(R.drawable.status_signature_revoked_cutout_24dp); - vStatus.setColorFilter( - context.getResources().getColor(R.color.key_flag_gray), - PorterDuff.Mode.SRC_IN); - } else if (isNotYetValid || isExpired) { - vStatus.setImageResource(R.drawable.status_signature_expired_cutout_24dp); - vStatus.setColorFilter( - context.getResources().getColor(R.color.key_flag_gray), - PorterDuff.Mode.SRC_IN); - } else if (!subKey.is_secure()) { - vStatus.setImageResource(R.drawable.status_signature_invalid_cutout_24dp); - vStatus.setColorFilter( - context.getResources().getColor(R.color.key_flag_gray), - PorterDuff.Mode.SRC_IN); - } - } else { - vStatus.setVisibility(View.GONE); - - vKeyId.setTextColor(mDefaultTextColor); - vKeyDetails.setTextColor(mDefaultTextColor); - vKeyExpiry.setTextColor(mDefaultTextColor); - - vCertifyIcon.clearColorFilter(); - vSignIcon.clearColorFilter(); - vEncryptIcon.clearColorFilter(); - vAuthenticateIcon.clearColorFilter(); - } - vKeyId.setEnabled(!isInvalid); - vKeyDetails.setEnabled(!isInvalid); - vKeyExpiry.setEnabled(!isInvalid); - - return view; - } - - // Disable selection of items, http://stackoverflow.com/a/4075045 - @Override - public boolean areAllItemsEnabled() { - return mSkpBuilder != null && super.areAllItemsEnabled(); - } - - // Disable selection of items, http://stackoverflow.com/a/4075045 - @Override - public boolean isEnabled(int position) { - return mSkpBuilder != null && super.isEnabled(position); - } - - /** 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 builder The parcel to get info from, or null to leave edit mode. - */ - public void setEditMode(@Nullable SaveKeyringParcel.Builder builder) { - mSkpBuilder = builder; - notifyDataSetChanged(); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java index 68dcb8c92..c0d956f99 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java @@ -17,6 +17,12 @@ package org.sufficientlysecure.keychain.ui.adapter; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + import android.app.Activity; import android.content.Context; import android.graphics.Typeface; @@ -26,7 +32,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; @@ -36,25 +41,18 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.TimeZone; - public class SubkeysAddedAdapter extends ArrayAdapter { private LayoutInflater mInflater; private Activity mActivity; - private boolean mNewKeyring; - public SubkeysAddedAdapter(Activity activity, List data, - boolean newKeyring) { + public SubkeysAddedAdapter(Activity activity, List data) { super(activity, -1, data); mActivity = activity; mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mNewKeyring = newKeyring; } static class ViewHolder { + public View itemView; public TextView vKeyId; public TextView vKeyDetails; public TextView vKeyExpiry; @@ -62,7 +60,6 @@ public class SubkeysAddedAdapter extends ArrayAdapter + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_subkey_item.xml b/OpenKeychain/src/main/res/layout/view_key_adv_subkey_item.xml index c9eb37f5e..82d76e07e 100644 --- a/OpenKeychain/src/main/res/layout/view_key_adv_subkey_item.xml +++ b/OpenKeychain/src/main/res/layout/view_key_adv_subkey_item.xml @@ -1,46 +1,23 @@ - - - - - - - - - + android:singleLine="true" + android:background="?android:selectableItemBackground"> + android:layout_alignParentStart="true"> + + + + + + + + + + - - diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_subkeys_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_adv_subkeys_fragment.xml index db25e3bbe..31dafd15a 100644 --- a/OpenKeychain/src/main/res/layout/view_key_adv_subkeys_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_adv_subkeys_fragment.xml @@ -24,37 +24,12 @@ xmlns:tools="http://schemas.android.com/tools"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:text="@string/section_keys" - android:layout_weight="1" /> + android:text="@string/section_keys" /> - - - - - - - - - - + android:layout_height="wrap_content" /> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 7cbe10136..d4f0c620e 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -179,6 +179,8 @@ "Expiry" "Valid from" "Usage" + Revoked + Insecure "Key Size" "Elliptic Curve" "Primary identity" @@ -2056,4 +2058,9 @@ "Thanks for helping out! You can change this preference in the settings." "That's alright, we won't ask again. You can change your mind in the settings." "Settings" + Subkey will be created + Subkey will be revoked + Subkey will be stripped + Expiry will change to never + Expiry will change to %s