use KeyAdapter in KeySpinner

This commit is contained in:
Vincent Breitmoser 2015-06-16 12:38:33 +02:00
parent b87a7372c9
commit f677446d51
10 changed files with 146 additions and 259 deletions

View File

@ -74,6 +74,10 @@ public class ClipboardReflection {
Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip"); Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip");
Object clipData = methodGetPrimaryClip.invoke(clipboard); Object clipData = methodGetPrimaryClip.invoke(clipboard);
if (clipData == null) {
return null;
}
// ClipData.Item clipDataItem = clipData.getItemAt(0); // ClipData.Item clipDataItem = clipData.getItemAt(0);
Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", int.class); Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", int.class);
Object clipDataItem = methodGetItemAt.invoke(clipData, 0); Object clipDataItem = methodGetItemAt.invoke(clipData, 0);

View File

@ -167,7 +167,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
@Override @Override
public long getAsymmetricSigningKeyId() { public long getAsymmetricSigningKeyId() {
return mSignKeySpinner.getSelectedItemId(); return mSignKeySpinner.getSelectedKeyId();
} }
@Override @Override

View File

@ -240,7 +240,7 @@ public class EncryptTextFragment
boolean gotEncryptionKeys = (encryptionKeyIds != null boolean gotEncryptionKeys = (encryptionKeyIds != null
&& encryptionKeyIds.length > 0); && encryptionKeyIds.length > 0);
if (!gotEncryptionKeys && signingKeyId == 0L) { if (!gotEncryptionKeys && signingKeyId == Constants.key.none) {
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR) Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
.show(this); .show(this);
return null; return null;

View File

@ -19,15 +19,16 @@ package org.sufficientlysecure.keychain.ui.adapter;
import java.io.Serializable; import java.io.Serializable;
import java.util.Calendar; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.TimeZone; import java.util.List;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.text.format.DateFormat;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -60,7 +61,6 @@ public class KeyAdapter extends CursorAdapter {
KeyRings.VERIFIED, KeyRings.VERIFIED,
KeyRings.HAS_ANY_SECRET, KeyRings.HAS_ANY_SECRET,
KeyRings.HAS_DUPLICATE_USER_ID, KeyRings.HAS_DUPLICATE_USER_ID,
KeyRings.HAS_ENCRYPT,
KeyRings.FINGERPRINT, KeyRings.FINGERPRINT,
KeyRings.CREATION, KeyRings.CREATION,
}; };
@ -72,9 +72,8 @@ public class KeyAdapter extends CursorAdapter {
public static final int INDEX_VERIFIED = 5; public static final int INDEX_VERIFIED = 5;
public static final int INDEX_HAS_ANY_SECRET = 6; public static final int INDEX_HAS_ANY_SECRET = 6;
public static final int INDEX_HAS_DUPLICATE_USER_ID = 7; public static final int INDEX_HAS_DUPLICATE_USER_ID = 7;
public static final int INDEX_HAS_ENCRYPT = 8; public static final int INDEX_FINGERPRINT = 8;
public static final int INDEX_FINGERPRINT = 9; public static final int INDEX_CREATION = 9;
public static final int INDEX_CREATION = 10;
public KeyAdapter(Context context, Cursor c, int flags) { public KeyAdapter(Context context, Cursor c, int flags) {
super(context, c, flags); super(context, c, flags);
@ -87,6 +86,7 @@ public class KeyAdapter extends CursorAdapter {
} }
public static class KeyItemViewHolder { public static class KeyItemViewHolder {
public View mView;
public Long mMasterKeyId; public Long mMasterKeyId;
public TextView mMainUserId; public TextView mMainUserId;
public TextView mMainUserIdRest; public TextView mMainUserIdRest;
@ -96,6 +96,7 @@ public class KeyAdapter extends CursorAdapter {
public ImageButton mSlingerButton; public ImageButton mSlingerButton;
public KeyItemViewHolder(View view) { public KeyItemViewHolder(View view) {
mView = view;
mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name); mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email); mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon); mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon);
@ -104,11 +105,10 @@ public class KeyAdapter extends CursorAdapter {
mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation); mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation);
} }
public void setData(Context context, Cursor cursor, Highlighter highlighter) { public void setData(Context context, KeyItem item, Highlighter highlighter) {
{ // set name and stuff, common to both key types { // set name and stuff, common to both key types
String userId = cursor.getString(INDEX_USER_ID); KeyRing.UserId userIdSplit = item.mUserId;
KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
if (userIdSplit.name != null) { if (userIdSplit.name != null) {
mMainUserId.setText(highlighter.highlight(userIdSplit.name)); mMainUserId.setText(highlighter.highlight(userIdSplit.name));
} else { } else {
@ -124,30 +124,23 @@ public class KeyAdapter extends CursorAdapter {
{ // set edit button and status, specific by key type { // set edit button and status, specific by key type
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); mMasterKeyId = item.mKeyId;
boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0;
boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;
boolean hasDuplicate = cursor.getInt(INDEX_HAS_DUPLICATE_USER_ID) != 0;
mMasterKeyId = masterKeyId;
// Note: order is important! // Note: order is important!
if (isRevoked) { if (item.mIsRevoked) {
KeyFormattingUtils KeyFormattingUtils
.setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray); .setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray);
mStatus.setVisibility(View.VISIBLE); mStatus.setVisibility(View.VISIBLE);
mSlinger.setVisibility(View.GONE); mSlinger.setVisibility(View.GONE);
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray)); mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
} else if (isExpired) { } else if (item.mIsExpired) {
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray); KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray);
mStatus.setVisibility(View.VISIBLE); mStatus.setVisibility(View.VISIBLE);
mSlinger.setVisibility(View.GONE); mSlinger.setVisibility(View.GONE);
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray)); mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
} else if (isSecret) { } else if (item.mIsSecret) {
mStatus.setVisibility(View.GONE); mStatus.setVisibility(View.GONE);
if (mSlingerButton.hasOnClickListeners()) { if (mSlingerButton.hasOnClickListeners()) {
mSlinger.setVisibility(View.VISIBLE); mSlinger.setVisibility(View.VISIBLE);
@ -158,7 +151,7 @@ public class KeyAdapter extends CursorAdapter {
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black)); mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
} else { } else {
// this is a public key - show if it's verified // this is a public key - show if it's verified
if (isVerified) { if (item.mIsVerified) {
KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED); KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED);
mStatus.setVisibility(View.VISIBLE); mStatus.setVisibility(View.VISIBLE);
} else { } else {
@ -170,9 +163,9 @@ public class KeyAdapter extends CursorAdapter {
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black)); mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
} }
if (hasDuplicate) { if (item.mHasDuplicate) {
String dateTime = DateUtils.formatDateTime(context, String dateTime = DateUtils.formatDateTime(context,
cursor.getLong(INDEX_CREATION) * 1000, item.mCreation.getTime(),
DateUtils.FORMAT_SHOW_DATE DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_YEAR
| DateUtils.FORMAT_ABBREV_MONTH); | DateUtils.FORMAT_ABBREV_MONTH);
@ -190,6 +183,10 @@ public class KeyAdapter extends CursorAdapter {
} }
public boolean isEnabled(Cursor cursor) {
return true;
}
@Override @Override
public View newView(Context context, Cursor cursor, ViewGroup parent) { public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.key_list_item, parent, false); View view = mInflater.inflate(R.layout.key_list_item, parent, false);
@ -204,7 +201,8 @@ public class KeyAdapter extends CursorAdapter {
public void bindView(View view, Context context, Cursor cursor) { public void bindView(View view, Context context, Cursor cursor) {
Highlighter highlighter = new Highlighter(context, mQuery); Highlighter highlighter = new Highlighter(context, mQuery);
KeyItemViewHolder h = (KeyItemViewHolder) view.getTag(); KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
h.setData(context, cursor, highlighter); KeyItem item = new KeyItem(cursor);
h.setData(context, item, highlighter);
} }
public boolean isSecretAvailable(int id) { public boolean isSecretAvailable(int id) {
@ -234,8 +232,9 @@ public class KeyAdapter extends CursorAdapter {
@Override @Override
public long getItemId(int position) { public long getItemId(int position) {
Cursor cursor = getCursor();
// prevent a crash on rapid cursor changes // prevent a crash on rapid cursor changes
if (getCursor().isClosed()) { if (cursor != null && getCursor().isClosed()) {
return 0L; return 0L;
} }
return super.getItemId(position); return super.getItemId(position);
@ -250,6 +249,7 @@ public class KeyAdapter extends CursorAdapter {
public final boolean mHasDuplicate; public final boolean mHasDuplicate;
public final Date mCreation; public final Date mCreation;
public final String mFingerprint; public final String mFingerprint;
public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified;
private KeyItem(Cursor cursor) { private KeyItem(Cursor cursor) {
String userId = cursor.getString(INDEX_USER_ID); String userId = cursor.getString(INDEX_USER_ID);
@ -260,6 +260,10 @@ public class KeyAdapter extends CursorAdapter {
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000); mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
mFingerprint = KeyFormattingUtils.convertFingerprintToHex( mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
cursor.getBlob(INDEX_FINGERPRINT)); cursor.getBlob(INDEX_FINGERPRINT));
mIsSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
mIsExpired = cursor.getInt(INDEX_IS_EXPIRED) > 0;
mIsVerified = cursor.getInt(INDEX_VERIFIED) > 0;
} }
public KeyItem(CanonicalizedPublicKeyRing ring) { public KeyItem(CanonicalizedPublicKeyRing ring) {
@ -272,6 +276,12 @@ public class KeyAdapter extends CursorAdapter {
mCreation = key.getCreationTime(); mCreation = key.getCreationTime();
mFingerprint = KeyFormattingUtils.convertFingerprintToHex( mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
ring.getFingerprint()); ring.getFingerprint());
mIsRevoked = key.isRevoked();
mIsExpired = key.isExpired();
// these two are actually "don't know"s
mIsSecret = false;
mIsVerified = false;
} }
public String getReadableName() { public String getReadableName() {
@ -284,4 +294,11 @@ public class KeyAdapter extends CursorAdapter {
} }
public static String[] getProjectionWith(String[] projection) {
List<String> list = new ArrayList<>();
list.addAll(Arrays.asList(PROJECTION));
list.addAll(Arrays.asList(projection));
return list.toArray(new String[list.size()]);
}
} }

View File

@ -17,6 +17,10 @@
package org.sufficientlysecure.keychain.ui.widget; package org.sufficientlysecure.keychain.ui.widget;
import java.util.Arrays;
import java.util.List;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
@ -24,14 +28,11 @@ import android.os.Bundle;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.ImageView;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
public class CertifyKeySpinner extends KeySpinner { public class CertifyKeySpinner extends KeySpinner {
private long mHiddenMasterKeyId = Constants.key.none; private long mHiddenMasterKeyId = Constants.key.none;
@ -59,19 +60,9 @@ public class CertifyKeySpinner extends KeySpinner {
// sample only has one Loader, so we don't care about the ID. // sample only has one Loader, so we don't care about the ID.
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri(); Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
// These are the rows that we will retrieve. String[] projection = KeyAdapter.getProjectionWith(new String[] {
String[] projection = new String[]{
KeychainContract.KeyRings._ID,
KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.KeyRings.KEY_ID,
KeychainContract.KeyRings.USER_ID,
KeychainContract.KeyRings.IS_REVOKED,
KeychainContract.KeyRings.IS_EXPIRED,
KeychainContract.KeyRings.HAS_CERTIFY, KeychainContract.KeyRings.HAS_CERTIFY,
KeychainContract.KeyRings.HAS_ANY_SECRET, });
KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID,
KeychainContract.KeyRings.CREATION
};
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND " String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND "
+ KeychainDatabase.Tables.KEYS + "." + KeychainContract.KeyRings.MASTER_KEY_ID + KeychainDatabase.Tables.KEYS + "." + KeychainContract.KeyRings.MASTER_KEY_ID
@ -82,7 +73,7 @@ public class CertifyKeySpinner extends KeySpinner {
return new CursorLoader(getContext(), baseUri, projection, where, null, null); return new CursorLoader(getContext(), baseUri, projection, where, null, null);
} }
private int mIndexHasCertify, mIndexIsRevoked, mIndexIsExpired; private int mIndexHasCertify;
@Override @Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) { public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
@ -90,8 +81,6 @@ public class CertifyKeySpinner extends KeySpinner {
if (loader.getId() == LOADER_ID) { if (loader.getId() == LOADER_ID) {
mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY); mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY);
mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED);
mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED);
// If: // If:
// - no key has been pre-selected (e.g. by SageSlinger) // - no key has been pre-selected (e.g. by SageSlinger)
@ -119,18 +108,15 @@ public class CertifyKeySpinner extends KeySpinner {
@Override @Override
boolean setStatus(Context context, Cursor cursor, ImageView statusView) { boolean isItemEnabled(Cursor cursor) {
if (cursor.getInt(mIndexIsRevoked) != 0) { if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray);
return false; return false;
} }
if (cursor.getInt(mIndexIsExpired) != 0) { if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray);
return false; return false;
} }
// don't invalidate the "None" entry, which is also null! // don't invalidate the "None" entry, which is also null!
if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) { if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray);
return false; return false;
} }

View File

@ -38,6 +38,7 @@ import com.tokenautocomplete.TokenCompleteTextView;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
@ -126,7 +127,13 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// These are the rows that we will retrieve. // These are the rows that we will retrieve.
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND " + KeyRings.IS_EXPIRED + " = 0 AND "
String[] projection = KeyAdapter.getProjectionWith(new String[] {
KeychainContract.KeyRings.HAS_ENCRYPT,
});
String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND "
+ KeyRings.IS_EXPIRED + " = 0 AND "
+ Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0"; + Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0";
if (args != null && args.containsKey(ARG_QUERY)) { if (args != null && args.containsKey(ARG_QUERY)) {
@ -135,12 +142,12 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
where += " AND " + KeyRings.USER_ID + " LIKE ?"; where += " AND " + KeyRings.USER_ID + " LIKE ?";
return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, return new CursorLoader(getContext(), baseUri, projection, where,
new String[]{"%" + query + "%"}, null); new String[]{"%" + query + "%"}, null);
} }
mAdapter.setSearchQuery(null); mAdapter.setSearchQuery(null);
return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, null, null); return new CursorLoader(getContext(), baseUri, projection, where, null, null);
} }

View File

@ -17,33 +17,30 @@
package org.sufficientlysecure.keychain.ui.widget; package org.sufficientlysecure.keychain.ui.widget;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.AppCompatSpinner; import android.support.v7.widget.AppCompatSpinner;
import android.text.format.DateUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.SpinnerAdapter; import android.widget.SpinnerAdapter;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.util.Log; 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. * Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
@ -119,7 +116,8 @@ public abstract class KeySpinner extends AppCompatSpinner implements
if (getContext() instanceof FragmentActivity) { if (getContext() instanceof FragmentActivity) {
((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this); ((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
} else { } else {
Log.e(Constants.TAG, "KeySpinner must be attached to FragmentActivity, this is " + getContext().getClass()); throw new AssertionError("KeySpinner must be attached to FragmentActivity, this is "
+ getContext().getClass());
} }
} }
@ -138,7 +136,11 @@ public abstract class KeySpinner extends AppCompatSpinner implements
} }
public long getSelectedKeyId() { public long getSelectedKeyId() {
return getSelectedItemId(); Object item = getSelectedItem();
if (item instanceof KeyItem) {
return ((KeyItem) item).mKeyId;
}
return Constants.key.none;
} }
public void setPreSelectedKeyId(long selectedKeyId) { public void setPreSelectedKeyId(long selectedKeyId) {
@ -146,161 +148,87 @@ public abstract class KeySpinner extends AppCompatSpinner implements
} }
protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter { protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter {
private CursorAdapter inner; private KeyAdapter inner;
private int mIndexUserId;
private int mIndexDuplicate;
private int mIndexMasterKeyId; private int mIndexMasterKeyId;
private int mIndexCreationDate;
public SelectKeyAdapter() { public SelectKeyAdapter() {
inner = new CursorAdapter(getContext(), null, 0) { inner = new KeyAdapter(getContext(), null, 0) {
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return View.inflate(getContext(), R.layout.keyspinner_item, null);
}
@Override @Override
public void bindView(View view, Context context, Cursor cursor) { public boolean isEnabled(Cursor cursor) {
TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name); return KeySpinner.this.isItemEnabled(cursor);
ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
TextView vDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
KeyRing.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexUserId));
vKeyName.setText(userId.name);
vKeyEmail.setText(userId.email);
boolean duplicate = cursor.getLong(mIndexDuplicate) > 0;
if (duplicate) {
String dateTime = DateUtils.formatDateTime(context,
cursor.getLong(mIndexCreationDate) * 1000,
DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_YEAR
| DateUtils.FORMAT_ABBREV_MONTH);
vDuplicate.setText(context.getString(R.string.label_key_created, dateTime));
vDuplicate.setVisibility(View.VISIBLE);
} else {
vDuplicate.setVisibility(View.GONE);
}
boolean valid = setStatus(getContext(), cursor, vKeyStatus);
setItemEnabled(view, valid);
} }
@Override
public long getItemId(int position) {
try {
return ((Cursor) getItem(position)).getLong(mIndexMasterKeyId);
} catch (Exception e) {
// This can happen on concurrent modification :(
return Constants.key.none;
}
}
}; };
} }
private void setItemEnabled(View view, boolean enabled) {
TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);
ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
if (enabled) {
vKeyName.setTextColor(Color.BLACK);
vKeyEmail.setTextColor(Color.BLACK);
vKeyDuplicate.setTextColor(Color.BLACK);
vKeyStatus.setVisibility(View.GONE);
view.setClickable(false);
} else {
vKeyName.setTextColor(Color.GRAY);
vKeyEmail.setTextColor(Color.GRAY);
vKeyDuplicate.setTextColor(Color.GRAY);
vKeyStatus.setVisibility(View.VISIBLE);
// this is a HACK. the trick is, if the element itself is clickable, the
// click is not passed on to the view list
view.setClickable(true);
}
}
public Cursor swapCursor(Cursor newCursor) { public Cursor swapCursor(Cursor newCursor) {
if (newCursor == null) return inner.swapCursor(null); if (newCursor == null) return inner.swapCursor(null);
mIndexDuplicate = newCursor.getColumnIndex(KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID);
mIndexUserId = newCursor.getColumnIndex(KeychainContract.KeyRings.USER_ID);
mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID); mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID);
mIndexCreationDate = newCursor.getColumnIndex(KeychainContract.KeyRings.CREATION);
Cursor oldCursor = inner.swapCursor(newCursor);
// pre-select key if mPreSelectedKeyId is given // pre-select key if mPreSelectedKeyId is given
if (mPreSelectedKeyId != Constants.key.none && newCursor.moveToFirst()) { if (mPreSelectedKeyId != Constants.key.none && newCursor.moveToFirst()) {
do { do {
if (newCursor.getLong(mIndexMasterKeyId) == mPreSelectedKeyId) { if (newCursor.getLong(mIndexMasterKeyId) == mPreSelectedKeyId) {
setSelection(newCursor.getPosition() + 1); setSelection(newCursor.getPosition() +1);
} }
} while (newCursor.moveToNext()); } while (newCursor.moveToNext());
} }
return inner.swapCursor(newCursor); return oldCursor;
} }
@Override @Override
public int getCount() { public int getCount() {
return inner.getCount() + 1; return inner.getCount() +1;
} }
@Override @Override
public Object getItem(int position) { public Object getItem(int position) {
if (position == 0) return null; if (position == 0) {
return inner.getItem(position - 1); return null;
}
return inner.getItem(position -1);
} }
@Override @Override
public long getItemId(int position) { public long getItemId(int position) {
if (position == 0) return Constants.key.none; if (position == 0) {
return inner.getItemId(position - 1); return Constants.key.none;
}
return inner.getItemId(position -1);
} }
@SuppressLint("ViewHolder") // inflate call is for the preview only
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
try {
View v = getDropDownView(position, convertView, parent);
v.findViewById(R.id.keyspinner_key_email).setVisibility(View.GONE);
return v;
} catch (NullPointerException e) {
// This is for the preview...
return View.inflate(getContext(), android.R.layout.simple_list_item_1, null);
}
}
@Override // Unfortunately, SpinnerAdapter does not support multiple view
public View getDropDownView(int position, View convertView, ViewGroup parent) { // types. For this reason, we throw away convertViews of a bad
View view; // type. This is sort of a hack, but since the number of elements
if (position == 0) { // we deal with in KeySpinners is usually very small (number of
if (convertView == null) { // secret keys), this is the easiest solution. (I'm sorry.)
view = inner.newView(null, null, parent); if (convertView != null) {
} else { // This assumes that the inner view has non-null tags on its views!
view = convertView; boolean isWrongType = (convertView.getTag() == null) != (position == 0);
if (isWrongType) {
convertView = null;
} }
TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);
ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
vKeyName.setText(R.string.choice_none);
vKeyEmail.setVisibility(View.GONE);
vKeyDuplicate.setVisibility(View.GONE);
vKeyStatus.setVisibility(View.GONE);
setItemEnabled(view, true);
} else {
view = inner.getView(position - 1, convertView, parent);
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
vKeyEmail.setVisibility(View.VISIBLE);
} }
return view;
if (position > 0) {
return inner.getView(position -1, convertView, parent);
}
return convertView != null ? convertView :
LayoutInflater.from(getContext()).inflate(
R.layout.keyspinner_item_none, parent, false);
} }
} }
boolean setStatus(Context context, Cursor cursor, ImageView statusView) { boolean isItemEnabled(Cursor cursor) {
return true; return true;
} }

View File

@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui.widget; package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
@ -24,12 +25,9 @@ import android.os.Bundle;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.ImageView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
public class SignKeySpinner extends KeySpinner { public class SignKeySpinner extends KeySpinner {
public SignKeySpinner(Context context) { public SignKeySpinner(Context context) {
@ -50,19 +48,9 @@ public class SignKeySpinner extends KeySpinner {
// sample only has one Loader, so we don't care about the ID. // sample only has one Loader, so we don't care about the ID.
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri(); Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
// These are the rows that we will retrieve. String[] projection = KeyAdapter.getProjectionWith(new String[] {
String[] projection = new String[]{
KeychainContract.KeyRings._ID,
KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.KeyRings.KEY_ID,
KeychainContract.KeyRings.USER_ID,
KeychainContract.KeyRings.IS_REVOKED,
KeychainContract.KeyRings.IS_EXPIRED,
KeychainContract.KeyRings.HAS_SIGN, KeychainContract.KeyRings.HAS_SIGN,
KeychainContract.KeyRings.HAS_ANY_SECRET, });
KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID,
KeychainContract.KeyRings.CREATION
};
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1"; String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1";
@ -71,7 +59,7 @@ public class SignKeySpinner extends KeySpinner {
return new CursorLoader(getContext(), baseUri, projection, where, null, null); return new CursorLoader(getContext(), baseUri, projection, where, null, null);
} }
private int mIndexHasSign, mIndexIsRevoked, mIndexIsExpired; private int mIndexHasSign;
@Override @Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) { public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
@ -79,23 +67,18 @@ public class SignKeySpinner extends KeySpinner {
if (loader.getId() == LOADER_ID) { if (loader.getId() == LOADER_ID) {
mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN); mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN);
mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED);
mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED);
} }
} }
@Override @Override
boolean setStatus(Context context, Cursor cursor, ImageView statusView) { boolean isItemEnabled(Cursor cursor) {
if (cursor.getInt(mIndexIsRevoked) != 0) { if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray);
return false; return false;
} }
if (cursor.getInt(mIndexIsExpired) != 0) { if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray);
return false; return false;
} }
if (cursor.getInt(mIndexHasSign) == 0) { if (cursor.getInt(mIndexHasSign) == 0) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray);
return false; return false;
} }

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:singleLine="true"
android:orientation="horizontal"
android:descendantFocusability="blocksDescendants"
android:focusable="false"
android:minHeight="44dip">
<LinearLayout
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:focusable="true"
android:orientation="vertical"
android:paddingLeft="8dp"
android:paddingRight="4dp">
<TextView
android:id="@+id/keyspinner_key_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_main_user_id"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/keyspinner_key_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:text="user@example.com"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/keyspinner_duplicate"
android:text="creation: bla"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<ImageView
android:id="@+id/keyspinner_key_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/status_signature_revoked_cutout_24dp"
android:paddingLeft="16dp"
android:paddingRight="16dp" />
</LinearLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:singleLine="true"
android:orientation="horizontal"
android:minHeight="44dip"
android:paddingLeft="8dp"
android:paddingRight="4dp">
<TextView
android:id="@+id/keyspinner_key_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/choice_none"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>