rewrite EncryptKeyCompletionView with generalized KeyAdapter

Conflicts:
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
This commit is contained in:
Vincent Breitmoser 2015-03-30 02:42:16 +02:00
parent 6b48ddd717
commit dcaac4f85f
7 changed files with 428 additions and 410 deletions

View File

@ -28,10 +28,14 @@ import com.tokenautocomplete.TokenCompleteTextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
import org.sufficientlysecure.keychain.util.Log;
@ -63,7 +67,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
try {
mEncryptInterface = (EncryptActivityInterface) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
throw new ClassCastException(activity + " must implement EncryptActivityInterface");
}
}
@ -110,14 +114,14 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() {
@Override
public void onTokenAdded(Object token) {
if (token instanceof EncryptKeyCompletionView.EncryptionKey) {
if (token instanceof KeyItem) {
updateEncryptionKeys();
}
}
@Override
public void onTokenRemoved(Object token) {
if (token instanceof EncryptKeyCompletionView.EncryptionKey) {
if (token instanceof KeyItem) {
updateEncryptionKeys();
}
}
@ -148,10 +152,10 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
if (encryptionKeyIds != null) {
for (long preselectedId : encryptionKeyIds) {
try {
CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing(
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(preselectedId));
mEncryptKeyView.addObject(mEncryptKeyView.new EncryptionKey(ring));
} catch (PgpKeyNotFoundException e) {
CanonicalizedPublicKeyRing ring =
mProviderHelper.getCanonicalizedPublicKeyRing(preselectedId);
mEncryptKeyView.addObject(new KeyItem(ring));
} catch (NotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
}
}
@ -159,6 +163,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
mEncryptKeyView.requestFocus();
updateEncryptionKeys();
}
}
private void updateEncryptionKeys() {
@ -166,9 +171,9 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
List<Long> keyIds = new ArrayList<>();
List<String> userIds = new ArrayList<>();
for (Object object : objects) {
if (object instanceof EncryptKeyCompletionView.EncryptionKey) {
keyIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getKeyId());
userIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getUserId());
if (object instanceof KeyItem) {
keyIds.add(((KeyItem) object).mKeyId);
userIds.add(((KeyItem) object).mUserIdFull);
}
}
long[] keyIdsArr = new long[keyIds.size()];

View File

@ -1,6 +1,6 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
* Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014-2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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
@ -26,7 +26,6 @@ import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@ -37,7 +36,6 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.SearchView;
import android.view.ActionMode;
import android.view.LayoutInflater;
@ -49,8 +47,6 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
@ -64,7 +60,6 @@ import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
@ -75,9 +70,8 @@ import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Highlighter;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.ExportHelper;
import org.sufficientlysecure.keychain.util.FabContainer;
@ -292,26 +286,6 @@ public class KeyListFragment extends LoaderFragment
getLoaderManager().initLoader(0, null, this);
}
// These are the rows that we will retrieve.
static final String[] PROJECTION = new String[]{
KeyRings._ID,
KeyRings.MASTER_KEY_ID,
KeyRings.USER_ID,
KeyRings.IS_REVOKED,
KeyRings.IS_EXPIRED,
KeyRings.VERIFIED,
KeyRings.HAS_ANY_SECRET,
KeyRings.HAS_DUPLICATE_USER_ID,
};
static final int INDEX_MASTER_KEY_ID = 1;
static final int INDEX_USER_ID = 2;
static final int INDEX_IS_REVOKED = 3;
static final int INDEX_IS_EXPIRED = 4;
static final int INDEX_VERIFIED = 5;
static final int INDEX_HAS_ANY_SECRET = 6;
static final int INDEX_HAS_DUPLICATE_USER_ID = 7;
static final String ORDER =
KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC";
@ -339,7 +313,8 @@ public class KeyListFragment extends LoaderFragment
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getActivity(), baseUri, PROJECTION, where, whereArgs, ORDER);
return new CursorLoader(getActivity(), baseUri,
KeyListAdapter.PROJECTION, where, whereArgs, ORDER);
}
@Override
@ -787,148 +762,54 @@ public class KeyListFragment extends LoaderFragment
anim.start();
}
/**
* Implements StickyListHeadersAdapter from library
*/
private class KeyListAdapter extends CursorAdapter implements StickyListHeadersAdapter {
private String mQuery;
private LayoutInflater mInflater;
public class KeyListAdapter extends KeyAdapter implements StickyListHeadersAdapter {
private HashMap<Integer, Boolean> mSelection = new HashMap<>();
public KeyListAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
}
public void setSearchQuery(String query) {
mQuery = query;
}
@Override
public Cursor swapCursor(Cursor newCursor) {
return super.swapCursor(newCursor);
}
private class ItemViewHolder {
Long mMasterKeyId;
TextView mMainUserId;
TextView mMainUserIdRest;
ImageView mStatus;
View mSlinger;
ImageButton mSlingerButton;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.key_list_item, parent, false);
final ItemViewHolder holder = new ItemViewHolder();
holder.mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
holder.mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
holder.mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon);
holder.mSlinger = view.findViewById(R.id.key_list_item_slinger_view);
holder.mSlingerButton = (ImageButton) view.findViewById(R.id.key_list_item_slinger_button);
holder.mSlingerButton.setColorFilter(context.getResources().getColor(R.color.tertiary_text_light),
PorterDuff.Mode.SRC_IN);
view.setTag(holder);
view.findViewById(R.id.key_list_item_slinger_button).setOnClickListener(new OnClickListener() {
View view = super.newView(context, cursor, parent);
final KeyItemViewHolder holder = (KeyItemViewHolder) view.getTag();
holder.mSlinger.setVisibility(View.VISIBLE);
holder.mSlingerButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (holder.mMasterKeyId != null) {
Intent safeSlingerIntent = new Intent(getActivity(), SafeSlingerActivity.class);
Intent safeSlingerIntent = new Intent(mContext, SafeSlingerActivity.class);
safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, holder.mMasterKeyId);
startActivityForResult(safeSlingerIntent, REQUEST_ACTION);
}
}
});
return view;
}
/**
* Bind cursor data to the item list view
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
Highlighter highlighter = new Highlighter(context, mQuery);
ItemViewHolder h = (ItemViewHolder) view.getTag();
public View getView(int position, View convertView, ViewGroup parent) {
// let the adapter handle setting up the row views
View v = super.getView(position, convertView, parent);
{ // set name and stuff, common to both key types
String userId = cursor.getString(INDEX_USER_ID);
KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
if (userIdSplit.name != null) {
h.mMainUserId.setText(highlighter.highlight(userIdSplit.name));
} else {
h.mMainUserId.setText(R.string.user_id_no_name);
}
if (userIdSplit.email != null) {
h.mMainUserIdRest.setText(highlighter.highlight(userIdSplit.email));
h.mMainUserIdRest.setVisibility(View.VISIBLE);
} else {
h.mMainUserIdRest.setVisibility(View.GONE);
}
}
{ // set edit button and status, specific by key type
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
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) == 1;
h.mMasterKeyId = masterKeyId;
// Note: order is important!
if (isRevoked) {
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, State.REVOKED, R.color.bg_gray);
h.mStatus.setVisibility(View.VISIBLE);
h.mSlinger.setVisibility(View.GONE);
h.mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
h.mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
} else if (isExpired) {
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, State.EXPIRED, R.color.bg_gray);
h.mStatus.setVisibility(View.VISIBLE);
h.mSlinger.setVisibility(View.GONE);
h.mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
h.mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
} else if (isSecret) {
h.mStatus.setVisibility(View.GONE);
h.mSlinger.setVisibility(View.VISIBLE);
h.mMainUserId.setTextColor(context.getResources().getColor(R.color.black));
h.mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
} else {
// this is a public key - show if it's verified
if (isVerified) {
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, State.VERIFIED);
h.mStatus.setVisibility(View.VISIBLE);
} else {
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, State.UNVERIFIED);
h.mStatus.setVisibility(View.VISIBLE);
}
h.mSlinger.setVisibility(View.GONE);
h.mMainUserId.setTextColor(context.getResources().getColor(R.color.black));
h.mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
}
if (mSelection.get(position) != null) {
// selected position color
v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis));
} else {
// default color
v.setBackgroundColor(Color.TRANSPARENT);
}
return v;
}
public boolean isSecretAvailable(int id) {
if (!mCursor.moveToPosition(id)) {
throw new IllegalStateException("couldn't move cursor to position " + id);
}
return mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
}
public long getMasterKeyId(int id) {
if (!mCursor.moveToPosition(id)) {
throw new IllegalStateException("couldn't move cursor to position " + id);
}
return mCursor.getLong(INDEX_MASTER_KEY_ID);
private class HeaderViewHolder {
TextView mText;
TextView mCount;
}
/**
@ -961,10 +842,10 @@ public class KeyListFragment extends LoaderFragment
throw new IllegalStateException("couldn't move cursor to position " + position);
}
if (mCursor.getInt(KeyListFragment.INDEX_HAS_ANY_SECRET) != 0) {
if (mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0) {
{ // set contact count
int num = mCursor.getCount();
String contactsTotal = getResources().getQuantityString(R.plurals.n_keys, num, num);
String contactsTotal = mContext.getResources().getQuantityString(R.plurals.n_keys, num, num);
holder.mCount.setText(contactsTotal);
holder.mCount.setVisibility(View.VISIBLE);
}
@ -974,7 +855,7 @@ public class KeyListFragment extends LoaderFragment
}
// set header text as first char in user id
String userId = mCursor.getString(KeyListFragment.INDEX_USER_ID);
String userId = mCursor.getString(INDEX_USER_ID);
String headerText = convertView.getResources().getString(R.string.user_id_no_name);
if (userId != null && userId.length() > 0) {
headerText = "" + userId.charAt(0);
@ -1000,11 +881,11 @@ public class KeyListFragment extends LoaderFragment
}
// early breakout: all secret keys are assigned id 0
if (mCursor.getInt(KeyListFragment.INDEX_HAS_ANY_SECRET) != 0) {
if (mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0) {
return 1L;
}
// otherwise, return the first character of the name as ID
String userId = mCursor.getString(KeyListFragment.INDEX_USER_ID);
String userId = mCursor.getString(INDEX_USER_ID);
if (userId != null && userId.length() > 0) {
return Character.toUpperCase(userId.charAt(0));
} else {
@ -1012,11 +893,6 @@ public class KeyListFragment extends LoaderFragment
}
}
private class HeaderViewHolder {
TextView mText;
TextView mCount;
}
/**
* -------------------------- MULTI-SELECTION METHODS --------------
*/
@ -1027,7 +903,7 @@ public class KeyListFragment extends LoaderFragment
public boolean isAnySecretSelected() {
for (int pos : mSelection.keySet()) {
if (mAdapter.isSecretAvailable(pos))
if (isSecretAvailable(pos))
return true;
}
return false;
@ -1038,7 +914,7 @@ public class KeyListFragment extends LoaderFragment
int i = 0;
// get master key ids
for (int pos : mSelection.keySet()) {
ids[i++] = mAdapter.getMasterKeyId(pos);
ids[i++] = getMasterKeyId(pos);
}
return ids;
}
@ -1053,26 +929,6 @@ public class KeyListFragment extends LoaderFragment
notifyDataSetChanged();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// let the adapter handle setting up the row views
View v = super.getView(position, convertView, parent);
/**
* Change color for multi-selection
*/
if (mSelection.get(position) != null) {
// selected position color
v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis));
} else {
// default color
v.setBackgroundColor(Color.TRANSPARENT);
}
return v;
}
}
}

View File

@ -0,0 +1,279 @@
/*
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.adapter;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import android.content.Context;
import android.database.Cursor;
import android.graphics.PorterDuff;
import android.support.v4.widget.CursorAdapter;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.ui.util.Highlighter;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
public class KeyAdapter extends CursorAdapter {
protected String mQuery;
protected LayoutInflater mInflater;
// These are the rows that we will retrieve.
public static final String[] PROJECTION = new String[]{
KeyRings._ID,
KeyRings.MASTER_KEY_ID,
KeyRings.USER_ID,
KeyRings.IS_REVOKED,
KeyRings.IS_EXPIRED,
KeyRings.VERIFIED,
KeyRings.HAS_ANY_SECRET,
KeyRings.HAS_DUPLICATE_USER_ID,
KeyRings.HAS_ENCRYPT,
KeyRings.FINGERPRINT,
KeyRings.CREATION,
};
public static final int INDEX_MASTER_KEY_ID = 1;
public static final int INDEX_USER_ID = 2;
public static final int INDEX_IS_REVOKED = 3;
public static final int INDEX_IS_EXPIRED = 4;
public static final int INDEX_VERIFIED = 5;
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_ENCRYPT = 8;
public static final int INDEX_FINGERPRINT = 9;
public static final int INDEX_CREATION = 10;
public KeyAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
}
public void setSearchQuery(String query) {
mQuery = query;
}
public static class KeyItemViewHolder {
public Long mMasterKeyId;
public TextView mMainUserId;
public TextView mMainUserIdRest;
public ImageView mStatus;
public View mSlinger;
public ImageButton mSlingerButton;
public KeyItemViewHolder(View view) {
mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon);
mSlinger = view.findViewById(R.id.key_list_item_slinger_view);
mSlingerButton = (ImageButton) view.findViewById(R.id.key_list_item_slinger_button);
}
public void setData(Context context, Cursor cursor, Highlighter highlighter) {
{ // set name and stuff, common to both key types
String userId = cursor.getString(INDEX_USER_ID);
KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
if (userIdSplit.name != null) {
mMainUserId.setText(highlighter.highlight(userIdSplit.name));
} else {
mMainUserId.setText(R.string.user_id_no_name);
}
if (userIdSplit.email != null) {
mMainUserIdRest.setText(highlighter.highlight(userIdSplit.email));
mMainUserIdRest.setVisibility(View.VISIBLE);
} else {
mMainUserIdRest.setVisibility(View.GONE);
}
}
{ // set edit button and status, specific by key type
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
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) == 1;
mMasterKeyId = masterKeyId;
// Note: order is important!
if (isRevoked) {
KeyFormattingUtils
.setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray);
mStatus.setVisibility(View.VISIBLE);
mSlinger.setVisibility(View.GONE);
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
} else if (isExpired) {
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray);
mStatus.setVisibility(View.VISIBLE);
mSlinger.setVisibility(View.GONE);
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
} else if (isSecret) {
mStatus.setVisibility(View.GONE);
if (mSlingerButton.hasOnClickListeners()) {
mSlinger.setVisibility(View.VISIBLE);
}
mMainUserId.setTextColor(context.getResources().getColor(R.color.black));
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
} else {
// this is a public key - show if it's verified
if (isVerified) {
KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED);
mStatus.setVisibility(View.VISIBLE);
} else {
KeyFormattingUtils.setStatusImage(context, mStatus, State.UNVERIFIED);
mStatus.setVisibility(View.VISIBLE);
}
mSlinger.setVisibility(View.GONE);
mMainUserId.setTextColor(context.getResources().getColor(R.color.black));
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
}
}
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.key_list_item, parent, false);
KeyItemViewHolder holder = new KeyItemViewHolder(view);
view.setTag(holder);
holder.mSlingerButton.setColorFilter(context.getResources().getColor(R.color.tertiary_text_light),
PorterDuff.Mode.SRC_IN);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
Highlighter highlighter = new Highlighter(context, mQuery);
KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
h.setData(context, cursor, highlighter);
}
public boolean isSecretAvailable(int id) {
if (!mCursor.moveToPosition(id)) {
throw new IllegalStateException("couldn't move cursor to position " + id);
}
return mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
}
public long getMasterKeyId(int id) {
if (!mCursor.moveToPosition(id)) {
throw new IllegalStateException("couldn't move cursor to position " + id);
}
return mCursor.getLong(INDEX_MASTER_KEY_ID);
}
@Override
public KeyItem getItem(int position) {
Cursor c = getCursor();
if (c.isClosed() || !c.moveToPosition(position)) {
return null;
}
return new KeyItem(c);
}
@Override
public long getItemId(int position) {
// prevent a crash on rapid cursor changes
if (getCursor().isClosed()) {
return 0L;
}
return super.getItemId(position);
}
public static class KeyItem {
public final String mUserIdFull;
public final KeyRing.UserId mUserId;
public final long mKeyId;
public final boolean mHasDuplicate;
public final Date mCreation;
public final String mFingerprint;
private KeyItem(Cursor cursor) {
String userId = cursor.getString(INDEX_USER_ID);
mUserId = KeyRing.splitUserId(userId);
mUserIdFull = userId;
mKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
mHasDuplicate = cursor.getLong(INDEX_HAS_DUPLICATE_USER_ID) > 0;
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
cursor.getBlob(INDEX_FINGERPRINT));
}
public KeyItem(CanonicalizedPublicKeyRing ring) {
CanonicalizedPublicKey key = ring.getPublicKey();
String userId = key.getPrimaryUserIdWithFallback();
mUserId = KeyRing.splitUserId(userId);
mUserIdFull = userId;
mKeyId = ring.getMasterKeyId();
mHasDuplicate = false;
mCreation = key.getCreationTime();
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
ring.getFingerprint());
}
public String getReadableName() {
if (mUserId.name != null) {
return mUserId.name;
} else {
return mUserId.email;
}
}
public boolean hasDuplicate() {
return mHasDuplicate;
}
public String getCreationDate(Context context) {
Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
creationCal.setTime(mCreation);
// convert from UTC to time zone of device
creationCal.setTimeZone(TimeZone.getDefault());
return context.getString(R.string.label_creation) + ": "
+ DateFormat.getDateFormat(context).format(creationCal.getTime());
}
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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
@ -17,49 +18,42 @@
package org.sufficientlysecure.keychain.ui.widget;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImageView;
import android.widget.TextView;
import com.tokenautocomplete.FilteredArrayAdapter;
import com.tokenautocomplete.TokenCompleteTextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ContactHelper;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
public class EncryptKeyCompletionView extends TokenCompleteTextView {
public class EncryptKeyCompletionView extends TokenCompleteTextView
implements LoaderCallbacks<Cursor> {
public static final String ARG_QUERY = "query";
private KeyAdapter mAdapter;
private LoaderManager mLoaderManager;
private String mPrefix;
public EncryptKeyCompletionView(Context context) {
super(context);
initView();
@ -76,33 +70,31 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView {
}
private void initView() {
swapCursor(null);
setPrefix(getContext().getString(R.string.label_to) + " ");
allowDuplicates(false);
mAdapter = new KeyAdapter(getContext(), null, 0);
setAdapter(mAdapter);
}
@Override
public void setPrefix(String p) {
// this one is private in the superclass, but we need it here
mPrefix = p;
super.setPrefix(p);
}
@Override
protected View getViewForObject(Object object) {
if (object instanceof EncryptionKey) {
LayoutInflater l = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (object instanceof KeyItem) {
LayoutInflater l = LayoutInflater.from(getContext());
View view = l.inflate(R.layout.recipient_box_entry, null);
((TextView) view.findViewById(android.R.id.text1)).setText(((EncryptionKey) object).getPrimary());
setImageByKey((ImageView) view.findViewById(android.R.id.icon), (EncryptionKey) object);
((TextView) view.findViewById(android.R.id.text1)).setText(((KeyItem) object).getReadableName());
return view;
}
return null;
}
private void setImageByKey(ImageView view, EncryptionKey key) {
Bitmap photo = ContactHelper.getCachedPhotoByMasterKeyId(getContext().getContentResolver(), key.getKeyId());
if (photo != null) {
view.setImageBitmap(photo);
} else {
view.setImageResource(R.drawable.ic_generic_man);
}
}
@Override
protected Object defaultObject(String completionText) {
// TODO: We could try to automagically download the key if it's unknown but a key id
@ -115,46 +107,54 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mLoaderManager = ((FragmentActivity) getContext()).getSupportLoaderManager();
if (getContext() instanceof FragmentActivity) {
((FragmentActivity) getContext()).getSupportLoaderManager().initLoader(hashCode(), null, new LoaderManager.LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// These are the rows that we will retrieve.
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
String[] projection = new String[]{
KeyRings._ID,
KeyRings.MASTER_KEY_ID,
KeyRings.KEY_ID,
KeyRings.USER_ID,
KeyRings.FINGERPRINT,
KeyRings.IS_EXPIRED,
KeyRings.HAS_ENCRYPT,
KeyRings.HAS_DUPLICATE_USER_ID,
KeyRings.CREATION
};
String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND " + KeyRings.IS_EXPIRED + " = 0 AND "
+ Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0";
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
swapCursor(null);
}
});
mLoaderManager.initLoader(hashCode(), null, this);
} else {
Log.e(Constants.TAG, "EncryptKeyCompletionView must be attached to a FragmentActivity, this is " + getContext().getClass());
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mLoaderManager = null;
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// These are the rows that we will retrieve.
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND " + KeyRings.IS_EXPIRED + " = 0 AND "
+ Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0";
if (args != null && args.containsKey(ARG_QUERY)) {
String query = args.getString(ARG_QUERY);
mAdapter.setSearchQuery(query);
where += " AND " + KeyRings.USER_ID + " LIKE ?";
return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where,
new String[] { "%" + query + "%" }, null);
}
mAdapter.setSearchQuery(null);
return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
@Override
public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
super.onFocusChanged(hasFocus, direction, previous);
@ -164,147 +164,15 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView {
}
}
public void swapCursor(Cursor cursor) {
if (cursor == null) {
setAdapter(new EncryptKeyAdapter(Collections.<EncryptionKey>emptyList()));
return;
@Override
protected void performFiltering(CharSequence text, int start, int end, int keyCode) {
super.performFiltering(text, start, end, keyCode);
if (start < mPrefix.length()) {
start = mPrefix.length();
}
ArrayList<EncryptionKey> keys = new ArrayList<>();
while (cursor.moveToNext()) {
try {
EncryptionKey key = new EncryptionKey(cursor);
keys.add(key);
} catch (Exception e) {
Log.w(Constants.TAG, e);
return;
}
}
setAdapter(new EncryptKeyAdapter(keys));
Bundle args = new Bundle();
args.putString(ARG_QUERY, text.subSequence(start, end).toString());
mLoaderManager.restartLoader(hashCode(), args, this);
}
public class EncryptionKey {
private String mUserIdFull;
private KeyRing.UserId mUserId;
private long mKeyId;
private boolean mHasDuplicate;
private Date mCreation;
private String mFingerprint;
public EncryptionKey(String userId, long keyId, boolean hasDuplicate, Date creation, String fingerprint) {
mUserId = KeyRing.splitUserId(userId);
mUserIdFull = userId;
mKeyId = keyId;
mHasDuplicate = hasDuplicate;
mCreation = creation;
mFingerprint = fingerprint;
}
public EncryptionKey(Cursor cursor) {
this(cursor.getString(cursor.getColumnIndexOrThrow(KeyRings.USER_ID)),
cursor.getLong(cursor.getColumnIndexOrThrow(KeyRings.KEY_ID)),
cursor.getLong(cursor.getColumnIndexOrThrow(KeyRings.HAS_DUPLICATE_USER_ID)) > 0,
new Date(cursor.getLong(cursor.getColumnIndexOrThrow(KeyRings.CREATION)) * 1000),
KeyFormattingUtils.convertFingerprintToHex(
cursor.getBlob(cursor.getColumnIndexOrThrow(KeyRings.FINGERPRINT))));
}
public EncryptionKey(CachedPublicKeyRing ring) throws PgpKeyNotFoundException {
this(ring.getPrimaryUserId(), ring.extractOrGetMasterKeyId(), false, null,
KeyFormattingUtils.convertFingerprintToHex(ring.getFingerprint()));
}
public String getUserId() {
return mUserIdFull;
}
public String getFingerprint() {
return mFingerprint;
}
public String getPrimary() {
if (mUserId.name != null) {
return mUserId.name;
} else {
return mUserId.email;
}
}
public String getSecondary() {
if (mUserId.email != null) {
return mUserId.email;
} else {
return getCreationDate();
}
}
public String getTertiary() {
if (mUserId.name != null) {
return getCreationDate();
} else {
return null;
}
}
public long getKeyId() {
return mKeyId;
}
public String getCreationDate() {
if (mHasDuplicate) {
Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
creationCal.setTime(mCreation);
// convert from UTC to time zone of device
creationCal.setTimeZone(TimeZone.getDefault());
return getContext().getString(R.string.label_creation) + ": "
+ DateFormat.getDateFormat(getContext()).format(creationCal.getTime());
} else {
return null;
}
}
public String getKeyIdHex() {
return KeyFormattingUtils.beautifyKeyIdWithPrefix(getContext(), mKeyId);
}
public String getKeyIdHexShort() {
return KeyFormattingUtils.convertKeyIdToHexShort(mKeyId);
}
@Override
public String toString() {
return Long.toString(mKeyId);
}
}
private class EncryptKeyAdapter extends FilteredArrayAdapter<EncryptionKey> {
public EncryptKeyAdapter(List<EncryptionKey> objs) {
super(EncryptKeyCompletionView.this.getContext(), 0, 0, objs);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater l = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view;
if (convertView != null) {
view = convertView;
} else {
view = l.inflate(R.layout.recipient_selection_list_entry, null);
}
((TextView) view.findViewById(android.R.id.title)).setText(getItem(position).getPrimary());
((TextView) view.findViewById(android.R.id.text1)).setText(getItem(position).getSecondary());
((TextView) view.findViewById(android.R.id.text2)).setText(getItem(position).getTertiary());
setImageByKey((ImageView) view.findViewById(android.R.id.icon), getItem(position));
return view;
}
@Override
protected boolean keepObject(EncryptionKey obj, String mask) {
String m = mask.toLowerCase(Locale.ENGLISH);
return obj.getUserId().toLowerCase(Locale.ENGLISH).contains(m) ||
obj.getKeyIdHex().contains(m) ||
obj.getKeyIdHexShort().startsWith(m);
}
}
}

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
@ -69,7 +70,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/status_signature_revoked_cutout_24dp"
android:padding="16dp" />
android:padding="16dp"
tools:src="@drawable/status_signature_revoked_cutout_24dp"
/>
</LinearLayout>

View File

@ -19,5 +19,7 @@
android:layout_marginLeft="12dip"
android:cropToPadding="true"
android:background="#ccc"
android:scaleType="centerCrop" />
android:scaleType="centerCrop"
android:src="@drawable/ic_person_grey_24dp" />
</LinearLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dip"
@ -21,7 +22,8 @@
android:layout_height="wrap_content"
android:paddingLeft="8dip"
android:singleLine="true"
android:ellipsize="end" />
android:ellipsize="end"
tools:text="Alice" />
<TextView
android:id="@android:id/text1"
@ -32,7 +34,8 @@
android:paddingLeft="16dip"
android:singleLine="true"
android:ellipsize="end"
android:layout_marginTop="-4dip" />
android:layout_marginTop="-4dip"
tools:text="alice@example.com" />
<TextView
android:id="@android:id/text2"
@ -43,15 +46,18 @@
android:paddingLeft="16dip"
android:singleLine="true"
android:ellipsize="end"
android:layout_marginTop="-4dip" />
android:layout_marginTop="-4dip"
tools:text="Creation 12.03.2015" />
</LinearLayout>
<ImageView
android:id="@android:id/icon"
android:layout_width="56dip"
android:layout_height="56dip"
android:layout_marginLeft="12dip"
android:cropToPadding="true"
android:background="#ccc"
android:scaleType="centerCrop" />
android:id="@+id/status_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="16dp"
tools:src="@drawable/status_signature_revoked_cutout_24dp"
/>
</LinearLayout>