use LiveData for signing key spinners

This commit is contained in:
Vincent Breitmoser 2018-06-26 17:19:18 +02:00
parent 1635c261b8
commit 83d5aafadb
18 changed files with 492 additions and 536 deletions

View file

@ -25,6 +25,7 @@ import java.util.List;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.support.annotation.WorkerThread;
import com.squareup.sqldelight.SqlDelightQuery;
import org.bouncycastle.bcpg.ArmoredOutputStream;
@ -43,6 +44,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import timber.log.Timber;
@WorkerThread
public class KeyRepository extends AbstractDao {
final ContentResolver contentResolver;
final LocalPublicKeyStorage mLocalPublicKeyStorage;

View file

@ -127,7 +127,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
Vector<String> userIds = new Vector<>();
for (int i = 0; i < getListView().getCount(); ++i) {
if (getListView().isItemChecked(i)) {
userIds.add(mAdapter.getUserId(i));
userIds.add(spinnerAdapter.getUserId(i));
}
}

View file

@ -29,23 +29,16 @@ import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Adapter;
import android.text.format.DateUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
@ -57,6 +50,7 @@ import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity;
import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteDeduplicatePresenter.RemoteDeduplicateView;
import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter;
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration;
@ -263,94 +257,4 @@ public class RemoteDeduplicateActivity extends FragmentActivity {
}
}
private static class KeyChoiceAdapter extends Adapter<KeyChoiceViewHolder> {
private final LayoutInflater layoutInflater;
private final Resources resources;
private List<UnifiedKeyInfo> data;
private Drawable iconUnselected;
private Drawable iconSelected;
private Integer activeItem;
KeyChoiceAdapter(LayoutInflater layoutInflater, Resources resources) {
this.layoutInflater = layoutInflater;
this.resources = resources;
}
@NonNull
@Override
public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View keyChoiceItemView = layoutInflater.inflate(R.layout.duplicate_key_item, parent, false);
return new KeyChoiceViewHolder(keyChoiceItemView);
}
@Override
public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) {
UnifiedKeyInfo keyInfo = data.get(position);
Drawable icon = (activeItem != null && position == activeItem) ? iconSelected : iconUnselected;
holder.bind(keyInfo, icon);
}
@Override
public int getItemCount() {
return data != null ? data.size() : 0;
}
public void setData(List<UnifiedKeyInfo> data) {
this.data = data;
notifyDataSetChanged();
}
void setSelectionDrawable(Drawable drawable) {
ConstantState constantState = drawable.getConstantState();
if (constantState == null) {
return;
}
iconSelected = constantState.newDrawable(resources);
iconUnselected = constantState.newDrawable(resources);
DrawableCompat.setTint(iconUnselected.mutate(), ResourcesCompat.getColor(resources, R.color.md_grey_300, null));
notifyDataSetChanged();
}
void setActiveItem(Integer newActiveItem) {
Integer prevActiveItem = this.activeItem;
this.activeItem = newActiveItem;
if (prevActiveItem != null) {
notifyItemChanged(prevActiveItem);
}
if (newActiveItem != null) {
notifyItemChanged(newActiveItem);
}
}
}
private static class KeyChoiceViewHolder extends RecyclerView.ViewHolder {
private final TextView vName;
private final TextView vCreation;
private final ImageView vIcon;
KeyChoiceViewHolder(View itemView) {
super(itemView);
vName = itemView.findViewById(R.id.key_list_item_name);
vCreation = itemView.findViewById(R.id.key_list_item_creation);
vIcon = itemView.findViewById(R.id.key_list_item_icon);
}
void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) {
vName.setText(keyInfo.name());
Context context = vCreation.getContext();
String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(),
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME |
DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH);
vCreation.setText(context.getString(R.string.label_key_created, dateTime));
vIcon.setImageDrawable(selectionIcon);
}
}
}

View file

@ -20,8 +20,11 @@ package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.app.Activity;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Bundle;
@ -42,9 +45,10 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
import org.sufficientlysecure.keychain.util.Preferences;
@ -53,7 +57,7 @@ public class CertifyKeyFragment
private CheckBox mUploadKeyCheckbox;
private CertifyKeySpinner mCertifyKeySpinner;
private KeySpinner mCertifyKeySpinner;
private MultiUserIdsFragment mMultiUserIdsFragment;
@ -61,6 +65,13 @@ public class CertifyKeyFragment
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class);
LiveData<List<UnifiedKeyInfo>> liveData = viewModel.getGenericLiveData(requireContext(), () -> {
KeyRepository keyRepository = KeyRepository.create(requireContext());
return keyRepository.getAllUnifiedKeyInfoWithSecret();
});
liveData.observe(this, mCertifyKeySpinner::setData);
if (savedInstanceState == null) {
// preselect certify key id if given
long certifyKeyId = getActivity().getIntent()
@ -90,6 +101,8 @@ public class CertifyKeyFragment
mMultiUserIdsFragment = (MultiUserIdsFragment)
getChildFragmentManager().findFragmentById(R.id.multi_user_ids_fragment);
mCertifyKeySpinner.setShowNone(R.string.choice_select_cert);
// make certify image gray, like action icons
ImageView vActionCertifyImage =
view.findViewById(R.id.certify_key_action_certify_image);

View file

@ -22,7 +22,10 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -36,12 +39,12 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner.OnKeyChangedListener;
import org.sufficientlysecure.keychain.util.Passphrase;
import timber.log.Timber;
@ -57,9 +60,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
/**
* Creates new instance of this fragment
*/
public static EncryptModeAsymmetricFragment newInstance(long signatureKey, long[] encryptionKeyIds) {
EncryptModeAsymmetricFragment frag = new EncryptModeAsymmetricFragment();
@ -75,23 +75,21 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
mSignKeySpinner = view.findViewById(R.id.sign);
mSignKeySpinner = view.findViewById(R.id.sign_key_spinner);
mEncryptKeyView = view.findViewById(R.id.recipient_list);
mEncryptKeyView.setThreshold(1); // Start working from first character
final ViewAnimator vSignatureIcon = view.findViewById(R.id.result_signature_icon);
mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() {
@Override
public void onKeyChanged(long masterKeyId) {
int child = masterKeyId != Constants.key.none ? 1 : 0;
if (vSignatureIcon.getDisplayedChild() != child) {
vSignatureIcon.setDisplayedChild(child);
}
mSignKeySpinner.setOnKeyChangedListener(masterKeyId -> {
int child = masterKeyId != Constants.key.none ? 1 : 0;
if (vSignatureIcon.getDisplayedChild() != child) {
vSignatureIcon.setDisplayedChild(child);
}
});
mSignKeySpinner.setShowNone(R.string.cert_none);
final ViewAnimator vEncryptionIcon = view.findViewById(R.id.result_encryption_icon);
mEncryptKeyView.setTokenListener(new TokenListener<KeyItem>() {
@ -117,7 +115,12 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mKeyRepository = KeyRepository.create(getContext());
mKeyRepository = KeyRepository.create(requireContext());
GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class);
LiveData<List<UnifiedKeyInfo>> liveData = viewModel.getGenericLiveData(requireContext(),
mKeyRepository::getAllUnifiedKeyInfoWithSecret);
liveData.observe(this, mSignKeySpinner::setData);
// preselect keys given, from state or arguments
if (savedInstanceState == null) {

View file

@ -24,15 +24,12 @@ import java.util.List;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
import android.support.v4.app.FragmentActivity;
import android.support.v4.os.CancellationSignal;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@ -58,7 +55,6 @@ import eu.davidea.flexibleadapter.SelectableAdapter.Mode;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.operations.KeySyncOperation;
import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.KeySyncParcel;
@ -78,8 +74,8 @@ import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectio
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItemFactory;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel;
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.FabContainer;
@ -101,6 +97,9 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
private FloatingActionsMenu mFab;
private KeyRepository keyRepository;
private FlexibleKeyItemFactory flexibleKeyItemFactory;
private final ActionMode.Callback mActionCallback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
@ -244,36 +243,18 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
setLayoutManager(new LinearLayoutManager(activity));
KeyListViewModel keyListViewModel = ViewModelProviders.of(this).get(KeyListViewModel.class);
keyListViewModel.getLiveData(getContext()).observe(this, this::onLoadKeyItems);
keyRepository = KeyRepository.create(requireContext());
flexibleKeyItemFactory = new FlexibleKeyItemFactory(requireContext().getResources());
GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class);
LiveData<List<FlexibleKeyItem>> liveData = viewModel.getGenericLiveData(requireContext(), this::loadFlexibleKeyItems);
liveData.observe(this, this::onLoadKeyItems);
}
public static class KeyListViewModel extends ViewModel {
LiveData<List<FlexibleKeyItem>> liveData;
LiveData<List<FlexibleKeyItem>> getLiveData(Context context) {
if (liveData == null) {
liveData = new KeyListLiveData(context);
}
return liveData;
}
}
public static class KeyListLiveData extends AsyncTaskLiveData<List<FlexibleKeyItem>> {
private final KeyRepository keyRepository;
private FlexibleKeyItemFactory flexibleKeyItemFactory;
KeyListLiveData(@NonNull Context context) {
super(context, KeyRings.CONTENT_URI);
keyRepository = KeyRepository.create(context.getApplicationContext());
flexibleKeyItemFactory = new FlexibleKeyItemFactory(context.getResources());
}
@Override
protected List<FlexibleKeyItem> asyncLoadData() {
List<UnifiedKeyInfo> unifiedKeyInfo = keyRepository.getAllUnifiedKeyInfo();
return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo);
}
@WorkerThread
private List<FlexibleKeyItem> loadFlexibleKeyItems() {
List<UnifiedKeyInfo> unifiedKeyInfo = keyRepository.getAllUnifiedKeyInfo();
return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo);
}
private void onLoadKeyItems(List<FlexibleKeyItem> flexibleKeyItems) {

View file

@ -0,0 +1,115 @@
package org.sufficientlysecure.keychain.ui.adapter;
import java.util.List;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.support.annotation.NonNull;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Adapter;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter.KeyChoiceViewHolder;
public class KeyChoiceAdapter extends Adapter<KeyChoiceViewHolder> {
private final LayoutInflater layoutInflater;
private final Resources resources;
private List<UnifiedKeyInfo> data;
private Drawable iconUnselected;
private Drawable iconSelected;
private Integer activeItem;
public KeyChoiceAdapter(LayoutInflater layoutInflater, Resources resources) {
this.layoutInflater = layoutInflater;
this.resources = resources;
}
@NonNull
@Override
public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View keyChoiceItemView = layoutInflater.inflate(R.layout.duplicate_key_item, parent, false);
return new KeyChoiceViewHolder(keyChoiceItemView);
}
@Override
public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) {
UnifiedKeyInfo keyInfo = data.get(position);
Drawable icon = (activeItem != null && position == activeItem) ? iconSelected : iconUnselected;
holder.bind(keyInfo, icon);
}
@Override
public int getItemCount() {
return data != null ? data.size() : 0;
}
public void setData(List<UnifiedKeyInfo> data) {
this.data = data;
notifyDataSetChanged();
}
public void setSelectionDrawable(Drawable drawable) {
ConstantState constantState = drawable.getConstantState();
if (constantState == null) {
return;
}
iconSelected = constantState.newDrawable(resources);
iconUnselected = constantState.newDrawable(resources);
DrawableCompat.setTint(iconUnselected.mutate(), ResourcesCompat.getColor(resources, R.color.md_grey_300, null));
notifyDataSetChanged();
}
public void setActiveItem(Integer newActiveItem) {
Integer prevActiveItem = this.activeItem;
this.activeItem = newActiveItem;
if (prevActiveItem != null) {
notifyItemChanged(prevActiveItem);
}
if (newActiveItem != null) {
notifyItemChanged(newActiveItem);
}
}
public static class KeyChoiceViewHolder extends RecyclerView.ViewHolder {
private final TextView vName;
private final TextView vCreation;
private final ImageView vIcon;
KeyChoiceViewHolder(View itemView) {
super(itemView);
vName = itemView.findViewById(R.id.key_list_item_name);
vCreation = itemView.findViewById(R.id.key_list_item_creation);
vIcon = itemView.findViewById(R.id.key_list_item_icon);
}
void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) {
vName.setText(keyInfo.name());
Context context = vCreation.getContext();
String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(),
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME |
DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH);
vCreation.setText(context.getString(R.string.label_key_created, dateTime));
vIcon.setImageDrawable(selectionIcon);
}
}
}

View file

@ -0,0 +1,23 @@
package org.sufficientlysecure.keychain.ui.keyview;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModel;
import android.content.Context;
import org.sufficientlysecure.keychain.livedata.GenericLiveData;
import org.sufficientlysecure.keychain.livedata.GenericLiveData.GenericDataLoader;
/** A simple generic ViewModel that can be used if exactly one field of data needs to be stored. */
public class GenericViewModel extends ViewModel {
private LiveData genericLiveData;
public <T> LiveData<T> getGenericLiveData(Context context, GenericDataLoader<T> func) {
if (genericLiveData == null) {
genericLiveData = new GenericLiveData<>(context, null, func);
}
// noinspection unchecked
return genericLiveData;
}
}

View file

@ -19,8 +19,11 @@ package org.sufficientlysecure.keychain.ui.keyview;
import java.util.Collections;
import java.util.List;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
@ -52,6 +55,7 @@ import org.sufficientlysecure.keychain.linked.UriAttribute;
import org.sufficientlysecure.keychain.livedata.CertificationDao;
import org.sufficientlysecure.keychain.livedata.GenericLiveData;
import org.sufficientlysecure.keychain.model.Certification.CertDetails;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.KeyRepository;
@ -69,7 +73,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
import org.sufficientlysecure.keychain.ui.widget.CertListWidget;
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
import timber.log.Timber;
@ -118,11 +122,15 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB
isSecret = args.getBoolean(ARG_IS_SECRET);
masterKeyId = args.getLong(ARG_MASTER_KEY_ID);
}
IdentityDao identityDao = IdentityDao.getInstance(getContext());
GenericLiveData<LinkedIdInfo> uriAttributeLiveData =
new GenericLiveData<>(getContext(), null, () -> identityDao.getLinkedIdInfo(masterKeyId, lidRank));
uriAttributeLiveData.observe(this, this::onLinkedIdInfoLoaded);
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
LinkedIdViewModel viewModel = ViewModelProviders.of(this).get(LinkedIdViewModel.class);
viewModel.getLinkedIdInfo(requireContext(), masterKeyId, lidRank).observe(this, this::onLinkedIdInfoLoaded);
viewModel.getCertifyingKeys(requireContext()).observe(this, viewHolder.vKeySpinner::setData);
}
private void onLinkedIdInfoLoaded(LinkedIdInfo linkedIdInfo) {
@ -191,7 +199,7 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB
private ViewAnimator vButtonSwitcher;
private CertListWidget vLinkedCerts;
private CertifyKeySpinner vKeySpinner;
private KeySpinner vKeySpinner;
private final View vButtonVerify;
private final View vButtonRetry;
private final View vButtonConfirm;
@ -236,6 +244,7 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB
if (!isSecret) {
showButton(2);
if (!vKeySpinner.isSingleEntry()) {
vKeySpinner.setShowNone(R.string.choice_select_cert);
vKeySpinnerContainer.setVisibility(View.VISIBLE);
}
} else {
@ -351,10 +360,8 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB
viewHolder.vButtonRetry.setOnClickListener(v -> verifyResource());
viewHolder.vButtonConfirm.setOnClickListener(v -> initiateCertifying());
CertificationDao certificationDao = CertificationDao.getInstance(context);
LiveData<CertDetails> certDetailsLiveData = new GenericLiveData<>(
context, null, () -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank));
certDetailsLiveData.observe(this, this::onLoadCertDetails);
LinkedIdViewModel viewModel = ViewModelProviders.of(this).get(LinkedIdViewModel.class);
viewModel.getCertDetails(context, masterKeyId, lidRank).observe(this, this::onLoadCertDetails);
return root;
}
@ -483,4 +490,38 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB
return true;
}
public static class LinkedIdViewModel extends ViewModel {
LiveData<List<UnifiedKeyInfo>> certifyingKeysLiveData;
LiveData<CertDetails> certDetailsLiveData;
LiveData<LinkedIdInfo> linkedIfInfoLiveData;
LiveData<List<UnifiedKeyInfo>> getCertifyingKeys(Context context) {
if (certifyingKeysLiveData == null) {
certifyingKeysLiveData = new GenericLiveData<>(context, null, () -> {
KeyRepository keyRepository = KeyRepository.create(context);
return keyRepository.getAllUnifiedKeyInfoWithSecret();
});
}
return certifyingKeysLiveData;
}
LiveData<CertDetails> getCertDetails(Context context, long masterKeyId, int lidRank) {
if (certDetailsLiveData == null) {
CertificationDao certificationDao = CertificationDao.getInstance(context);
certDetailsLiveData = new GenericLiveData<>(context, null,
() -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank));
}
return certDetailsLiveData;
}
public LiveData<LinkedIdInfo> getLinkedIdInfo(Context context, long masterKeyId, int lidRank) {
if (linkedIfInfoLiveData == null) {
IdentityDao identityDao = IdentityDao.getInstance(context);
linkedIfInfoLiveData = new GenericLiveData<>(context, null,
() -> identityDao.getLinkedIdInfo(masterKeyId, lidRank));
}
return linkedIfInfoLiveData;
}
}
}

View file

@ -268,6 +268,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(this).get(UnifiedKeyInfoViewModel.class);
viewModel.setMasterKeyId(getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0L));
viewModel.getUnifiedKeyInfoLiveData(getApplicationContext()).observe(this, this::onLoadUnifiedKeyInfo);
if (savedInstanceState == null && intent.hasExtra(EXTRA_DISPLAY_RESULT)) {
OperationResult result = intent.getParcelableExtra(EXTRA_DISPLAY_RESULT);

View file

@ -1,136 +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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.StringRes;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.AttributeSet;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
public class CertifyKeySpinner extends KeySpinner {
private long mHiddenMasterKeyId = Constants.key.none;
private boolean mIsSingle;
public CertifyKeySpinner(Context context) {
super(context);
}
public CertifyKeySpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CertifyKeySpinner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setHiddenMasterKeyId(long hiddenMasterKeyId) {
this.mHiddenMasterKeyId = hiddenMasterKeyId;
reload();
}
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle data) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
String[] projection = KeyAdapter.getProjectionWith(new String[] {
KeychainContract.KeyRings.HAS_CERTIFY_SECRET,
});
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND "
+ KeychainDatabase.Tables.KEYS + "." + KeychainContract.KeyRings.MASTER_KEY_ID
+ " != " + mHiddenMasterKeyId;
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
}
private int mIndexHasCertify;
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
super.onLoadFinished(loader, data);
if (loader.getId() == LOADER_ID) {
mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY_SECRET);
// If:
// - no key has been pre-selected (e.g. by SageSlinger)
// - there are actually keys (not just "none" entry)
// Then:
// - select key that is capable of certifying, but only if there is only one key capable of it
if (mPreSelectedKeyId == Constants.key.none && mAdapter.getCount() > 1) {
// preselect if key can certify
int selection = -1;
while (data.moveToNext()) {
if (!data.isNull(mIndexHasCertify)) {
if (selection == -1) {
selection = data.getPosition() + 1;
mIsSingle = true;
} else {
// if selection is already set, we have more than one certify key!
// get back to "none"!
mIsSingle = false;
selection = 0;
}
}
}
setSelection(selection);
}
}
}
public boolean isSingleEntry() {
return mIsSingle && getSelectedItemPosition() != 0;
}
@Override
boolean isItemEnabled(Cursor cursor) {
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
return false;
}
if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
return false;
}
if (cursor.isNull(mIndexHasCertify)) {
return false;
}
// valid key
return true;
}
@Override
public @StringRes int getNoneString() {
return R.string.choice_select_cert;
}
}

View file

@ -0,0 +1,211 @@
package org.sufficientlysecure.keychain.ui.widget;
import java.util.List;
import android.content.Context;
import android.support.annotation.StringRes;
import android.text.format.DateUtils;
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.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
class KeyChoiceSpinnerAdapter extends BaseAdapter {
private Integer noneItemString;
private List<UnifiedKeyInfo> data;
private final LayoutInflater layoutInflater;
KeyChoiceSpinnerAdapter(Context context) {
super();
layoutInflater = LayoutInflater.from(context);
}
public void setData(List<UnifiedKeyInfo> data) {
this.data = data;
notifyDataSetChanged();
}
public void setNoneItemString(@StringRes Integer noneItemString) {
this.noneItemString = noneItemString;
notifyDataSetChanged();
}
public boolean hasNoneItem() {
return noneItemString != null;
}
@Override
public int getCount() {
return (data != null ? data.size() : 0) + (noneItemString != null ? 1 : 0);
}
public boolean isSingleEntry() {
return data != null && data.size() == 1;
}
@Override
public UnifiedKeyInfo getItem(int position) {
if (noneItemString != null) {
if (position == 0) {
return null;
}
position -= 1;
}
return data.get(position);
}
@Override
public long getItemId(int position) {
if (noneItemString != null) {
if (position == 0) {
return 0;
}
position -= 1;
}
return data != null ? data.get(position).master_key_id() : 0;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public int getItemViewType(int position) {
if (noneItemString != null && position == 0) {
return 1;
} else {
return super.getItemViewType(position);
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (noneItemString != null) {
if (position == 0) {
if (convertView != null && convertView.getTag() == null) {
return convertView;
} else {
View view = layoutInflater.inflate(R.layout.keyspinner_item_none, parent, false);
view.<TextView>findViewById(R.id.keyspinner_key_name).setText(noneItemString);
return view;
}
}
}
View view;
KeyChoiceViewHolder viewHolder;
if (convertView == null || !(convertView.getTag() instanceof KeyChoiceViewHolder)) {
view = layoutInflater.inflate(R.layout.key_list_item, parent, false);
viewHolder = new KeyChoiceViewHolder(view);
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = ((KeyChoiceViewHolder) view.getTag());
}
UnifiedKeyInfo keyInfo = getItem(position);
viewHolder.bind(view.getContext(), keyInfo, isEnabled(position));
return view;
}
public static class KeyChoiceViewHolder {
private View mView;
private TextView mMainUserId;
private TextView mMainUserIdRest;
private TextView mCreationDate;
private ImageView mStatus;
KeyChoiceViewHolder(View view) {
mView = view;
mMainUserId = view.findViewById(R.id.key_list_item_name);
mMainUserIdRest = view.findViewById(R.id.key_list_item_email);
mStatus = view.findViewById(R.id.key_list_item_status_icon);
mCreationDate = view.findViewById(R.id.key_list_item_creation);
}
public void bind(Context context, UnifiedKeyInfo keyInfo, boolean enabled) {
{ // set name and stuff, common to both key types
if (keyInfo.name() != null) {
mMainUserId.setText(keyInfo.name());
} else {
mMainUserId.setText(R.string.user_id_no_name);
}
if (keyInfo.email() != null) {
mMainUserIdRest.setText(keyInfo.email());
mMainUserIdRest.setVisibility(View.VISIBLE);
} else {
mMainUserIdRest.setVisibility(View.GONE);
}
}
// sort of a hack: if this item isn't enabled, we make it clickable
// to intercept its click events. either way, no listener!
mView.setClickable(!enabled);
{ // set edit button and status, specific by key type
int textColor;
// Note: order is important!
if (keyInfo.is_revoked()) {
KeyFormattingUtils
.setStatusImage(context, mStatus, null, State.REVOKED, R.color.key_flag_gray);
mStatus.setVisibility(View.VISIBLE);
textColor = context.getResources().getColor(R.color.key_flag_gray);
} else if (keyInfo.is_expired()) {
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.key_flag_gray);
mStatus.setVisibility(View.VISIBLE);
textColor = context.getResources().getColor(R.color.key_flag_gray);
} else if (!keyInfo.is_secure()) {
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.INSECURE, R.color.key_flag_gray);
mStatus.setVisibility(View.VISIBLE);
textColor = context.getResources().getColor(R.color.key_flag_gray);
} else if (keyInfo.has_any_secret()) {
mStatus.setVisibility(View.GONE);
textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText);
} else {
// this is a public key - show if it's verified
if (keyInfo.is_verified()) {
KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED);
mStatus.setVisibility(View.VISIBLE);
} else {
KeyFormattingUtils.setStatusImage(context, mStatus, State.UNVERIFIED);
mStatus.setVisibility(View.VISIBLE);
}
textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText);
}
mMainUserId.setTextColor(textColor);
mMainUserIdRest.setTextColor(textColor);
if (keyInfo.has_duplicate()) {
String dateTime = DateUtils.formatDateTime(context,
keyInfo.creation() * 1000,
DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR
| DateUtils.FORMAT_ABBREV_MONTH);
mCreationDate.setText(context.getString(R.string.label_key_created,
dateTime));
mCreationDate.setTextColor(textColor);
mCreationDate.setVisibility(View.VISIBLE);
} else {
mCreationDate.setVisibility(View.GONE);
}
}
}
}
}

View file

@ -18,39 +18,22 @@
package org.sufficientlysecure.keychain.ui.widget;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.widget.AppCompatSpinner;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
/**
* Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
* Related: http://stackoverflow.com/a/27713090
*/
public abstract class KeySpinner extends AppCompatSpinner implements
LoaderManager.LoaderCallbacks<Cursor> {
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
public class KeySpinner extends AppCompatSpinner {
public static final String ARG_SUPER_STATE = "super_state";
public static final String ARG_KEY_ID = "key_id";
@ -58,13 +41,10 @@ public abstract class KeySpinner extends AppCompatSpinner implements
void onKeyChanged(long masterKeyId);
}
protected long mPreSelectedKeyId = Constants.key.none;
protected SelectKeyAdapter mAdapter = new SelectKeyAdapter();
protected Long preSelectedKeyId;
protected KeyChoiceSpinnerAdapter spinnerAdapter;
protected OnKeyChangedListener mListener;
// this shall note collide with other loaders inside the activity
protected int LOADER_ID = 2343;
public KeySpinner(Context context) {
super(context);
initView();
@ -81,7 +61,9 @@ public abstract class KeySpinner extends AppCompatSpinner implements
}
private void initView() {
setAdapter(mAdapter);
spinnerAdapter = new KeyChoiceSpinnerAdapter(getContext());
setAdapter(spinnerAdapter);
super.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
@ -100,6 +82,10 @@ public abstract class KeySpinner extends AppCompatSpinner implements
});
}
public void setShowNone(@StringRes Integer noneStringRes) {
spinnerAdapter.setNoneItemString(noneStringRes);
}
@Override
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
throw new UnsupportedOperationException();
@ -109,32 +95,28 @@ public abstract class KeySpinner extends AppCompatSpinner implements
mListener = listener;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
reload();
public void setData(List<UnifiedKeyInfo> keyInfos) {
spinnerAdapter.setData(keyInfos);
maybeSelectPreselection(keyInfos);
}
public void reload() {
if (getContext() instanceof FragmentActivity) {
((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
} else {
// ignore, this happens during preview! we use fragmentactivities everywhere either way
private void maybeSelectPreselection(List<UnifiedKeyInfo> keyInfos) {
if (preSelectedKeyId == null) {
return;
}
for (UnifiedKeyInfo keyInfo : keyInfos) {
if (keyInfo.master_key_id() == preSelectedKeyId) {
int position = keyInfos.indexOf(keyInfo);
if (spinnerAdapter.hasNoneItem()) {
position += 1;
}
setSelection(position);
}
}
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (loader.getId() == LOADER_ID) {
mAdapter.swapCursor(data);
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
if (loader.getId() == LOADER_ID) {
mAdapter.swapCursor(null);
}
public boolean isSingleEntry() {
return spinnerAdapter.isSingleEntry();
}
public long getSelectedKeyId() {
@ -143,108 +125,21 @@ public abstract class KeySpinner extends AppCompatSpinner implements
}
public long getSelectedKeyId(Object item) {
if (item instanceof KeyItem) {
return ((KeyItem) item).mKeyId;
if (item instanceof UnifiedKeyInfo) {
return ((UnifiedKeyInfo) item).master_key_id();
}
return Constants.key.none;
}
public void setPreSelectedKeyId(long selectedKeyId) {
mPreSelectedKeyId = selectedKeyId;
}
protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter {
private KeyAdapter inner;
private int mIndexMasterKeyId;
public SelectKeyAdapter() {
inner = new KeyAdapter(getContext(), null, 0) {
@Override
public boolean isEnabled(Cursor cursor) {
return KeySpinner.this.isItemEnabled(cursor);
}
};
}
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == null) return inner.swapCursor(null);
mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID);
Cursor oldCursor = inner.swapCursor(newCursor);
// pre-select key if mPreSelectedKeyId is given
if (mPreSelectedKeyId != Constants.key.none && newCursor.moveToFirst()) {
do {
if (newCursor.getLong(mIndexMasterKeyId) == mPreSelectedKeyId) {
setSelection(newCursor.getPosition() +1);
}
} while (newCursor.moveToNext());
}
return oldCursor;
}
@Override
public int getCount() {
return inner.getCount() +1;
}
@Override
public KeyItem getItem(int position) {
if (position == 0) {
return null;
}
return inner.getItem(position -1);
}
@Override
public long getItemId(int position) {
if (position == 0) {
return Constants.key.none;
}
return inner.getItemId(position -1);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Unfortunately, SpinnerAdapter does not support multiple view
// types. For this reason, we throw away convertViews of a bad
// type. This is sort of a hack, but since the number of elements
// we deal with in KeySpinners is usually very small (number of
// secret keys), this is the easiest solution. (I'm sorry.)
if (convertView != null) {
// This assumes that the inner view has non-null tags on its views!
boolean isWrongType = (convertView.getTag() == null) != (position == 0);
if (isWrongType) {
convertView = null;
}
}
if (position > 0) {
return inner.getView(position -1, convertView, parent);
}
View view = convertView != null ? convertView :
LayoutInflater.from(getContext()).inflate(
R.layout.keyspinner_item_none, parent, false);
((TextView) view.findViewById(R.id.keyspinner_key_name)).setText(getNoneString());
return view;
}
}
boolean isItemEnabled(Cursor cursor) {
return true;
preSelectedKeyId = selectedKeyId;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
Bundle bundle = (Bundle) state;
mPreSelectedKeyId = bundle.getLong(ARG_KEY_ID);
preSelectedKeyId = bundle.getLong(ARG_KEY_ID);
// restore super state
super.onRestoreInstanceState(bundle.getParcelable(ARG_SUPER_STATE));
@ -262,9 +157,4 @@ public abstract class KeySpinner extends AppCompatSpinner implements
bundle.putLong(ARG_KEY_ID, getSelectedKeyId());
return bundle;
}
public @StringRes int getNoneString() {
return R.string.cert_none;
}
}

View file

@ -1,92 +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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.AttributeSet;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
public class SignKeySpinner extends KeySpinner {
public SignKeySpinner(Context context) {
super(context);
}
public SignKeySpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SignKeySpinner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle data) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
String[] projection = KeyAdapter.getProjectionWith(new String[] {
KeychainContract.KeyRings.HAS_SIGN_SECRET,
});
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1";
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
}
private int mIndexHasSign;
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
super.onLoadFinished(loader, data);
if (loader.getId() == LOADER_ID) {
mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN_SECRET);
}
}
@Override
boolean isItemEnabled(Cursor cursor) {
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
return false;
}
if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
return false;
}
if (cursor.getInt(KeyAdapter.INDEX_IS_SECURE) == 0) {
return false;
}
if (cursor.isNull(mIndexHasSign)) {
return false;
}
// valid key
return true;
}
}

View file

@ -49,7 +49,7 @@
android:paddingRight="8dp"
android:gravity="center_vertical" />
<org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner
<org.sufficientlysecure.keychain.ui.widget.KeySpinner
android:id="@+id/certify_key_spinner"
android:minHeight="56dip"
android:layout_width="match_parent"

View file

@ -95,8 +95,8 @@
android:text="@string/label_asymmetric_from"
android:paddingRight="8dp"/>
<org.sufficientlysecure.keychain.ui.widget.SignKeySpinner
android:id="@+id/sign"
<org.sufficientlysecure.keychain.ui.widget.KeySpinner
android:id="@+id/sign_key_spinner"
android:minHeight="56dip"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -6,7 +6,7 @@
android:gravity="center_vertical"
android:singleLine="true"
android:orientation="horizontal"
android:minHeight="44dip"
android:minHeight="?listPreferredItemHeight"
android:paddingLeft="8dp"
android:paddingRight="4dp">

View file

@ -132,11 +132,11 @@
android:layout_marginEnd="4dp"
android:text="@string/add_keys_my_key" />
<org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner
<org.sufficientlysecure.keychain.ui.widget.KeySpinner
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/cert_key_spinner">
</org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner>
</org.sufficientlysecure.keychain.ui.widget.KeySpinner>
</LinearLayout>