mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-17 14:25:08 -05:00
Merge branch 'development' into v/crypto-input-parcel
Conflicts: OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java
This commit is contained in:
commit
91494dd3a3
@ -28,10 +28,14 @@ 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.pgp.CanonicalizedPublicKey;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
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.EncryptKeyCompletionView;
|
||||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@ -91,7 +95,7 @@ public class EncryptModeAsymmetricFragment extends Fragment {
|
|||||||
try {
|
try {
|
||||||
mEncryptInterface = (IAsymmetric) activity;
|
mEncryptInterface = (IAsymmetric) activity;
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
throw new ClassCastException(activity.toString() + " must implement IAsymmetric");
|
throw new ClassCastException(activity + " must implement IAsymmetric");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,14 +132,14 @@ public class EncryptModeAsymmetricFragment extends Fragment {
|
|||||||
mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() {
|
mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTokenAdded(Object token) {
|
public void onTokenAdded(Object token) {
|
||||||
if (token instanceof EncryptKeyCompletionView.EncryptionKey) {
|
if (token instanceof KeyItem) {
|
||||||
updateEncryptionKeys();
|
updateEncryptionKeys();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTokenRemoved(Object token) {
|
public void onTokenRemoved(Object token) {
|
||||||
if (token instanceof EncryptKeyCompletionView.EncryptionKey) {
|
if (token instanceof KeyItem) {
|
||||||
updateEncryptionKeys();
|
updateEncryptionKeys();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,10 +166,10 @@ public class EncryptModeAsymmetricFragment extends Fragment {
|
|||||||
if (encryptionKeyIds != null) {
|
if (encryptionKeyIds != null) {
|
||||||
for (long preselectedId : encryptionKeyIds) {
|
for (long preselectedId : encryptionKeyIds) {
|
||||||
try {
|
try {
|
||||||
CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing(
|
CanonicalizedPublicKeyRing ring =
|
||||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(preselectedId));
|
mProviderHelper.getCanonicalizedPublicKeyRing(preselectedId);
|
||||||
mEncryptKeyView.addObject(mEncryptKeyView.new EncryptionKey(ring));
|
mEncryptKeyView.addObject(new KeyItem(ring));
|
||||||
} catch (PgpKeyNotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
Log.e(Constants.TAG, "key not found!", e);
|
Log.e(Constants.TAG, "key not found!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,6 +177,7 @@ public class EncryptModeAsymmetricFragment extends Fragment {
|
|||||||
mEncryptKeyView.requestFocus();
|
mEncryptKeyView.requestFocus();
|
||||||
updateEncryptionKeys();
|
updateEncryptionKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateEncryptionKeys() {
|
private void updateEncryptionKeys() {
|
||||||
@ -180,9 +185,9 @@ public class EncryptModeAsymmetricFragment extends Fragment {
|
|||||||
List<Long> keyIds = new ArrayList<>();
|
List<Long> keyIds = new ArrayList<>();
|
||||||
List<String> userIds = new ArrayList<>();
|
List<String> userIds = new ArrayList<>();
|
||||||
for (Object object : objects) {
|
for (Object object : objects) {
|
||||||
if (object instanceof EncryptKeyCompletionView.EncryptionKey) {
|
if (object instanceof KeyItem) {
|
||||||
keyIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getKeyId());
|
keyIds.add(((KeyItem) object).mKeyId);
|
||||||
userIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getUserId());
|
userIds.add(((KeyItem) object).mUserIdFull);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
long[] keyIdsArr = new long[keyIds.size()];
|
long[] keyIdsArr = new long[keyIds.size()];
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
* Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
* Copyright (C) 2014-2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
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.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.support.v4.view.MenuItemCompat;
|
import android.support.v4.view.MenuItemCompat;
|
||||||
import android.support.v4.widget.CursorAdapter;
|
|
||||||
import android.support.v7.widget.SearchView;
|
import android.support.v7.widget.SearchView;
|
||||||
import android.view.ActionMode;
|
import android.view.ActionMode;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -49,8 +47,6 @@ import android.view.View.OnClickListener;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
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.DeleteResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
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;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
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.service.PassphraseCacheService;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
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;
|
||||||
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.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.util.ExportHelper;
|
import org.sufficientlysecure.keychain.util.ExportHelper;
|
||||||
import org.sufficientlysecure.keychain.util.FabContainer;
|
import org.sufficientlysecure.keychain.util.FabContainer;
|
||||||
@ -292,26 +286,6 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
getLoaderManager().initLoader(0, null, this);
|
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 =
|
static final String ORDER =
|
||||||
KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC";
|
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
|
// Now create and return a CursorLoader that will take care of
|
||||||
// creating a Cursor for the data being displayed.
|
// 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
|
@Override
|
||||||
@ -787,148 +762,54 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
anim.start();
|
anim.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public class KeyListAdapter extends KeyAdapter implements StickyListHeadersAdapter {
|
||||||
* Implements StickyListHeadersAdapter from library
|
|
||||||
*/
|
|
||||||
private class KeyListAdapter extends CursorAdapter implements StickyListHeadersAdapter {
|
|
||||||
private String mQuery;
|
|
||||||
private LayoutInflater mInflater;
|
|
||||||
|
|
||||||
private HashMap<Integer, Boolean> mSelection = new HashMap<>();
|
private HashMap<Integer, Boolean> mSelection = new HashMap<>();
|
||||||
|
|
||||||
public KeyListAdapter(Context context, Cursor c, int flags) {
|
public KeyListAdapter(Context context, Cursor c, int flags) {
|
||||||
super(context, c, 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
|
@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 = super.newView(context, cursor, parent);
|
||||||
final ItemViewHolder holder = new ItemViewHolder();
|
|
||||||
holder.mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
|
final KeyItemViewHolder holder = (KeyItemViewHolder) view.getTag();
|
||||||
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.setVisibility(View.VISIBLE);
|
||||||
holder.mSlinger = view.findViewById(R.id.key_list_item_slinger_view);
|
holder.mSlingerButton.setOnClickListener(new OnClickListener() {
|
||||||
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() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (holder.mMasterKeyId != null) {
|
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);
|
safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, holder.mMasterKeyId);
|
||||||
startActivityForResult(safeSlingerIntent, REQUEST_ACTION);
|
startActivityForResult(safeSlingerIntent, REQUEST_ACTION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind cursor data to the item list view
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
Highlighter highlighter = new Highlighter(context, mQuery);
|
// let the adapter handle setting up the row views
|
||||||
ItemViewHolder h = (ItemViewHolder) view.getTag();
|
View v = super.getView(position, convertView, parent);
|
||||||
|
|
||||||
{ // set name and stuff, common to both key types
|
if (mSelection.get(position) != null) {
|
||||||
String userId = cursor.getString(INDEX_USER_ID);
|
// selected position color
|
||||||
KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
|
v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis));
|
||||||
if (userIdSplit.name != null) {
|
|
||||||
h.mMainUserId.setText(highlighter.highlight(userIdSplit.name));
|
|
||||||
} else {
|
} else {
|
||||||
h.mMainUserId.setText(R.string.user_id_no_name);
|
// default color
|
||||||
}
|
v.setBackgroundColor(Color.TRANSPARENT);
|
||||||
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
|
return v;
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private class HeaderViewHolder {
|
||||||
|
TextView mText;
|
||||||
public boolean isSecretAvailable(int id) {
|
TextView mCount;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -961,10 +842,10 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
throw new IllegalStateException("couldn't move cursor to position " + position);
|
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
|
{ // set contact count
|
||||||
int num = mCursor.getCount();
|
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.setText(contactsTotal);
|
||||||
holder.mCount.setVisibility(View.VISIBLE);
|
holder.mCount.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
@ -974,7 +855,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set header text as first char in user id
|
// 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);
|
String headerText = convertView.getResources().getString(R.string.user_id_no_name);
|
||||||
if (userId != null && userId.length() > 0) {
|
if (userId != null && userId.length() > 0) {
|
||||||
headerText = "" + userId.charAt(0);
|
headerText = "" + userId.charAt(0);
|
||||||
@ -1000,11 +881,11 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
// early breakout: all secret keys are assigned id 0
|
// 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;
|
return 1L;
|
||||||
}
|
}
|
||||||
// otherwise, return the first character of the name as ID
|
// 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) {
|
if (userId != null && userId.length() > 0) {
|
||||||
return Character.toUpperCase(userId.charAt(0));
|
return Character.toUpperCase(userId.charAt(0));
|
||||||
} else {
|
} else {
|
||||||
@ -1012,11 +893,6 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HeaderViewHolder {
|
|
||||||
TextView mText;
|
|
||||||
TextView mCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -------------------------- MULTI-SELECTION METHODS --------------
|
* -------------------------- MULTI-SELECTION METHODS --------------
|
||||||
*/
|
*/
|
||||||
@ -1027,7 +903,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
|
|
||||||
public boolean isAnySecretSelected() {
|
public boolean isAnySecretSelected() {
|
||||||
for (int pos : mSelection.keySet()) {
|
for (int pos : mSelection.keySet()) {
|
||||||
if (mAdapter.isSecretAvailable(pos))
|
if (isSecretAvailable(pos))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -1038,7 +914,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
// get master key ids
|
// get master key ids
|
||||||
for (int pos : mSelection.keySet()) {
|
for (int pos : mSelection.keySet()) {
|
||||||
ids[i++] = mAdapter.getMasterKeyId(pos);
|
ids[i++] = getMasterKeyId(pos);
|
||||||
}
|
}
|
||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
@ -1053,26 +929,6 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
notifyDataSetChanged();
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -17,49 +18,42 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui.widget;
|
package org.sufficientlysecure.keychain.ui.widget;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
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.app.LoaderManager.LoaderCallbacks;
|
||||||
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.text.format.DateFormat;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.tokenautocomplete.FilteredArrayAdapter;
|
|
||||||
import com.tokenautocomplete.TokenCompleteTextView;
|
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.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.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
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) {
|
public EncryptKeyCompletionView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
initView();
|
initView();
|
||||||
@ -76,33 +70,31 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initView() {
|
private void initView() {
|
||||||
swapCursor(null);
|
|
||||||
setPrefix(getContext().getString(R.string.label_to) + " ");
|
setPrefix(getContext().getString(R.string.label_to) + " ");
|
||||||
|
|
||||||
allowDuplicates(false);
|
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
|
@Override
|
||||||
protected View getViewForObject(Object object) {
|
protected View getViewForObject(Object object) {
|
||||||
if (object instanceof EncryptionKey) {
|
if (object instanceof KeyItem) {
|
||||||
LayoutInflater l = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
|
LayoutInflater l = LayoutInflater.from(getContext());
|
||||||
View view = l.inflate(R.layout.recipient_box_entry, null);
|
View view = l.inflate(R.layout.recipient_box_entry, null);
|
||||||
((TextView) view.findViewById(android.R.id.text1)).setText(((EncryptionKey) object).getPrimary());
|
((TextView) view.findViewById(android.R.id.text1)).setText(((KeyItem) object).getReadableName());
|
||||||
setImageByKey((ImageView) view.findViewById(android.R.id.icon), (EncryptionKey) object);
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
return null;
|
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
|
@Override
|
||||||
protected Object defaultObject(String completionText) {
|
protected Object defaultObject(String completionText) {
|
||||||
// TODO: We could try to automagically download the key if it's unknown but a key id
|
// TODO: We could try to automagically download the key if it's unknown but a key id
|
||||||
@ -115,44 +107,52 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView {
|
|||||||
@Override
|
@Override
|
||||||
protected void onAttachedToWindow() {
|
protected void onAttachedToWindow() {
|
||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
|
|
||||||
|
mLoaderManager = ((FragmentActivity) getContext()).getSupportLoaderManager();
|
||||||
|
|
||||||
if (getContext() instanceof FragmentActivity) {
|
if (getContext() instanceof FragmentActivity) {
|
||||||
((FragmentActivity) getContext()).getSupportLoaderManager().initLoader(hashCode(), null, new LoaderManager.LoaderCallbacks<Cursor>() {
|
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
|
@Override
|
||||||
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[] 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 "
|
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";
|
||||||
|
|
||||||
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
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
|
@Override
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||||
swapCursor(data);
|
mAdapter.swapCursor(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoaderReset(Loader<Cursor> loader) {
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
swapCursor(null);
|
mAdapter.swapCursor(null);
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Log.e(Constants.TAG, "EncryptKeyCompletionView must be attached to a FragmentActivity, this is " + getContext().getClass());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -164,147 +164,15 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void swapCursor(Cursor cursor) {
|
|
||||||
if (cursor == null) {
|
|
||||||
setAdapter(new EncryptKeyAdapter(Collections.<EncryptionKey>emptyList()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public String toString() {
|
protected void performFiltering(CharSequence text, int start, int end, int keyCode) {
|
||||||
return Long.toString(mKeyId);
|
super.performFiltering(text, start, end, keyCode);
|
||||||
|
if (start < mPrefix.length()) {
|
||||||
|
start = mPrefix.length();
|
||||||
}
|
}
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(ARG_QUERY, text.subSequence(start, end).toString());
|
||||||
|
mLoaderManager.restartLoader(hashCode(), args, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||||
@ -69,7 +70,8 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
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>
|
</LinearLayout>
|
||||||
|
@ -19,5 +19,7 @@
|
|||||||
android:layout_marginLeft="12dip"
|
android:layout_marginLeft="12dip"
|
||||||
android:cropToPadding="true"
|
android:cropToPadding="true"
|
||||||
android:background="#ccc"
|
android:background="#ccc"
|
||||||
android:scaleType="centerCrop" />
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/ic_person_grey_24dp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="48dip"
|
android:minHeight="48dip"
|
||||||
@ -21,7 +22,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingLeft="8dip"
|
android:paddingLeft="8dip"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:ellipsize="end" />
|
android:ellipsize="end"
|
||||||
|
tools:text="Alice" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@android:id/text1"
|
android:id="@android:id/text1"
|
||||||
@ -32,7 +34,8 @@
|
|||||||
android:paddingLeft="16dip"
|
android:paddingLeft="16dip"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:layout_marginTop="-4dip" />
|
android:layout_marginTop="-4dip"
|
||||||
|
tools:text="alice@example.com" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@android:id/text2"
|
android:id="@android:id/text2"
|
||||||
@ -43,15 +46,18 @@
|
|||||||
android:paddingLeft="16dip"
|
android:paddingLeft="16dip"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:layout_marginTop="-4dip" />
|
android:layout_marginTop="-4dip"
|
||||||
|
tools:text="Creation 12.03.2015" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@android:id/icon"
|
android:id="@+id/status_icon"
|
||||||
android:layout_width="56dip"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="56dip"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="12dip"
|
android:layout_gravity="center"
|
||||||
android:cropToPadding="true"
|
android:padding="16dp"
|
||||||
android:background="#ccc"
|
tools:src="@drawable/status_signature_revoked_cutout_24dp"
|
||||||
android:scaleType="centerCrop" />
|
/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
Loading…
Reference in New Issue
Block a user