diff options
author | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-03-18 14:43:20 +0100 |
---|---|---|
committer | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-03-18 14:43:20 +0100 |
commit | 711a51f7fc72c4c13ea63bd11f7aa67a21a3307c (patch) | |
tree | 56d72b6812fc9ddb301bd9921b69823575f6f52f /OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui | |
parent | 30644b5c4f72f828ecead09fc7e2d5d4ae8e91db (diff) | |
parent | 87004fd0ca3a5178fba3050599a445ba9c64662c (diff) | |
download | open-keychain-711a51f7fc72c4c13ea63bd11f7aa67a21a3307c.tar.gz open-keychain-711a51f7fc72c4c13ea63bd11f7aa67a21a3307c.tar.bz2 open-keychain-711a51f7fc72c4c13ea63bd11f7aa67a21a3307c.zip |
Merge pull request #430 from Valodim/certify
Revamp certify dialogue
Diffstat (limited to 'OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui')
4 files changed, 220 insertions, 79 deletions
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index 9a9911c94..d8df5ced3 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -19,11 +19,15 @@ package org.sufficientlysecure.keychain.ui; import android.app.ProgressDialog; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.view.View; @@ -32,26 +36,28 @@ import android.widget.*; import android.widget.CompoundButton.OnCheckedChangeListener; import com.beardedhen.androidbootstrap.BootstrapButton; import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSignature; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Log; -import java.util.Iterator; +import java.util.ArrayList; /** * Signs the specified public key with the specified secret master key */ public class CertifyKeyActivity extends ActionBarActivity implements - SelectSecretKeyLayoutFragment.SelectSecretKeyCallback { + SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> { private BootstrapButton mSignButton; private CheckBox mUploadKeyCheckbox; private Spinner mSelectKeyserverSpinner; @@ -62,6 +68,12 @@ public class CertifyKeyActivity extends ActionBarActivity implements private long mPubKeyId = 0; private long mMasterKeyId = 0; + private ListView mUserIds; + private ViewKeyUserIdsAdapter mUserIdsAdapter; + + private static final int LOADER_ID_KEYRING = 0; + private static final int LOADER_ID_USER_IDS = 1; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -125,9 +137,18 @@ public class CertifyKeyActivity extends ActionBarActivity implements finish(); return; } + Log.e(Constants.TAG, "uri: " + mDataUri); PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, mDataUri); + mUserIds = (ListView) findViewById(R.id.user_ids); + + mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0, true); + mUserIds.setAdapter(mUserIdsAdapter); + + getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this); + getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); + if (signKey != null) { mPubKeyId = PgpKeyHelper.getMasterKey(signKey).getKeyID(); } @@ -138,6 +159,76 @@ public class CertifyKeyActivity extends ActionBarActivity implements } } + static final String[] KEYRING_PROJECTION = + new String[] { + KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.Keys.FINGERPRINT, + KeychainContract.UserIds.USER_ID + }; + static final int INDEX_MASTER_KEY_ID = 1; + static final int INDEX_FINGERPRINT = 2; + static final int INDEX_USER_ID = 3; + + static final String[] USER_IDS_PROJECTION = + new String[]{ + KeychainContract.UserIds._ID, + KeychainContract.UserIds.USER_ID, + KeychainContract.UserIds.RANK + }; + static final String USER_IDS_SORT_ORDER = + KeychainContract.UserIds.RANK + " ASC"; + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + switch(id) { + case LOADER_ID_KEYRING: + return new CursorLoader(this, mDataUri, KEYRING_PROJECTION, null, null, null); + case LOADER_ID_USER_IDS: { + Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri); + return new CursorLoader(this, baseUri, USER_IDS_PROJECTION, null, null, USER_IDS_SORT_ORDER); + } + } + return null; + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + switch(loader.getId()) { + case LOADER_ID_KEYRING: + // the first key here is our master key + if (data.moveToFirst()) { + long keyId = data.getLong(INDEX_MASTER_KEY_ID); + String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId); + ((TextView) findViewById(R.id.key_id)).setText(keyIdStr); + + String mainUserId = data.getString(INDEX_USER_ID); + ((TextView) findViewById(R.id.main_user_id)).setText(mainUserId); + + byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT); + if (fingerprintBlob == null) { + // FALLBACK for old database entries + fingerprintBlob = ProviderHelper.getFingerprint(this, mDataUri); + } + String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true); + ((TextView) findViewById(R.id.fingerprint)).setText(OtherHelper.colorizeFingerprint(fingerprint)); + } + break; + case LOADER_ID_USER_IDS: + mUserIdsAdapter.swapCursor(data); + break; + } + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + switch(loader.getId()) { + case LOADER_ID_USER_IDS: + mUserIdsAdapter.swapCursor(null); + break; + } + } + private void showPassphraseDialog(final long secretKeyId) { // Message is received after passphrase is cached Handler returnHandler = new Handler() { @@ -173,6 +264,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements // if we have already signed this key, dont bother doing it again boolean alreadySigned = false; + /* todo: reconsider this at a later point when certs are in the db @SuppressWarnings("unchecked") Iterator<PGPSignature> itr = pubring.getPublicKey(mPubKeyId).getSignatures(); while (itr.hasNext()) { @@ -182,6 +274,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements break; } } + */ if (!alreadySigned) { /* @@ -209,6 +302,15 @@ public class CertifyKeyActivity extends ActionBarActivity implements * kicks off the actual signing process on a background thread */ private void startSigning() { + + // Bail out if there is not at least one user id selected + ArrayList<String> userIds = mUserIdsAdapter.getSelectedUserIds(); + if(userIds.isEmpty()) { + Toast.makeText(CertifyKeyActivity.this, "No User IDs to sign selected!", + Toast.LENGTH_SHORT).show(); + return; + } + // Send all information needed to service to sign key in other thread Intent intent = new Intent(this, KeychainIntentService.class); @@ -219,6 +321,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId); data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId); + data.putStringArrayList(KeychainIntentService.CERTIFY_KEY_UIDS, userIds); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java index beff8385e..3d22ca9f6 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java @@ -39,6 +39,7 @@ public class SelectSecretKeyLayoutFragment extends Fragment { private TextView mKeyUserId; private TextView mKeyUserIdRest; private TextView mKeyMasterKeyIdHex; + private TextView mNoKeySelected; private BootstrapButton mSelectKeyButton; private Boolean mFilterCertify; @@ -60,10 +61,10 @@ public class SelectSecretKeyLayoutFragment extends Fragment { public void selectKey(long secretKeyId) { if (secretKeyId == Id.key.none) { - mKeyMasterKeyIdHex.setText(R.string.api_settings_no_key); - mKeyUserIdRest.setText(""); + mNoKeySelected.setVisibility(View.VISIBLE); mKeyUserId.setVisibility(View.GONE); mKeyUserIdRest.setVisibility(View.GONE); + mKeyMasterKeyIdHex.setVisibility(View.GONE); } else { PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId( @@ -93,20 +94,25 @@ public class SelectSecretKeyLayoutFragment extends Fragment { mKeyMasterKeyIdHex.setText(masterkeyIdHex); mKeyUserId.setText(userName); mKeyUserIdRest.setText(userEmail); + mKeyMasterKeyIdHex.setVisibility(View.VISIBLE); mKeyUserId.setVisibility(View.VISIBLE); mKeyUserIdRest.setVisibility(View.VISIBLE); + mNoKeySelected.setVisibility(View.GONE); } else { - mKeyMasterKeyIdHex.setText(getActivity().getResources().getString(R.string.no_key)); + mKeyMasterKeyIdHex.setVisibility(View.GONE); mKeyUserId.setVisibility(View.GONE); mKeyUserIdRest.setVisibility(View.GONE); + mNoKeySelected.setVisibility(View.VISIBLE); } } else { mKeyMasterKeyIdHex.setText( getActivity().getResources() .getString(R.string.no_keys_added_or_updated) + " for master id: " + secretKeyId); + mKeyMasterKeyIdHex.setVisibility(View.VISIBLE); mKeyUserId.setVisibility(View.GONE); mKeyUserIdRest.setVisibility(View.GONE); + mNoKeySelected.setVisibility(View.GONE); } } @@ -124,6 +130,7 @@ public class SelectSecretKeyLayoutFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.select_secret_key_layout_fragment, container, false); + mNoKeySelected = (TextView) view.findViewById(R.id.no_key_selected); mKeyUserId = (TextView) view.findViewById(R.id.select_secret_key_user_id); mKeyUserIdRest = (TextView) view.findViewById(R.id.select_secret_key_user_id_rest); mKeyMasterKeyIdHex = (TextView) view.findViewById(R.id.select_secret_key_master_key_hex); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java index eeb17fea2..a828a4405 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java @@ -26,10 +26,7 @@ import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; -import android.text.Spannable; -import android.text.SpannableStringBuilder; import android.text.format.DateFormat; -import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -65,6 +62,7 @@ public class ViewKeyMainFragment extends Fragment implements private TextView mSecretKey; private BootstrapButton mActionEdit; private BootstrapButton mActionEncrypt; + private BootstrapButton mActionCertify; private ListView mUserIds; private ListView mKeys; @@ -95,6 +93,7 @@ public class ViewKeyMainFragment extends Fragment implements mKeys = (ListView) view.findViewById(R.id.keys); mActionEdit = (BootstrapButton) view.findViewById(R.id.action_edit); mActionEncrypt = (BootstrapButton) view.findViewById(R.id.action_encrypt); + mActionCertify = (BootstrapButton) view.findViewById(R.id.action_certify); return view; } @@ -131,6 +130,11 @@ public class ViewKeyMainFragment extends Fragment implements mSecretKey.setTextColor(getResources().getColor(R.color.emphasis)); mSecretKey.setText(R.string.secret_key_yes); + // certify button + // TODO this button MIGHT be useful if the user wants to + // certify a private key with another... + // mActionCertify.setVisibility(View.GONE); + // edit button mActionEdit.setVisibility(View.VISIBLE); mActionEdit.setOnClickListener(new View.OnClickListener() { @@ -147,8 +151,22 @@ public class ViewKeyMainFragment extends Fragment implements } else { mSecretKey.setTextColor(Color.BLACK); mSecretKey.setText(getResources().getString(R.string.secret_key_no)); + + // certify button + mActionCertify.setVisibility(View.VISIBLE); + // edit button mActionEdit.setVisibility(View.GONE); } + + // TODO see todo note above, doing this here for now + mActionCertify.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + certifyKey(KeychainContract.KeyRings.buildPublicKeyRingsByMasterKeyIdUri( + Long.toString(masterKeyId) + )); + } + }); + } mActionEncrypt.setOnClickListener(new View.OnClickListener() { @@ -180,12 +198,13 @@ public class ViewKeyMainFragment extends Fragment implements static final int KEYRING_INDEX_USER_ID = 2; static final String[] USER_IDS_PROJECTION = - new String[]{KeychainContract.UserIds._ID, KeychainContract.UserIds.USER_ID, - KeychainContract.UserIds.RANK, }; - // not the main user id - static final String USER_IDS_SELECTION = KeychainContract.UserIds.RANK + " > 0 "; + new String[]{ + KeychainContract.UserIds._ID, + KeychainContract.UserIds.USER_ID, + KeychainContract.UserIds.RANK, + }; static final String USER_IDS_SORT_ORDER = - KeychainContract.UserIds.USER_ID + " COLLATE LOCALIZED ASC"; + KeychainContract.UserIds.RANK + " COLLATE LOCALIZED ASC"; static final String[] KEYS_PROJECTION = new String[]{KeychainContract.Keys._ID, KeychainContract.Keys.KEY_ID, @@ -221,7 +240,7 @@ public class ViewKeyMainFragment extends Fragment implements // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, USER_IDS_SELECTION, null, + return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, null, null, USER_IDS_SORT_ORDER); } case LOADER_ID_KEYS: { @@ -303,7 +322,7 @@ public class ViewKeyMainFragment extends Fragment implements } String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true); - mFingerprint.setText(colorizeFingerprint(fingerprint)); + mFingerprint.setText(OtherHelper.colorizeFingerprint(fingerprint)); } mKeysAdapter.swapCursor(data); @@ -314,63 +333,6 @@ public class ViewKeyMainFragment extends Fragment implements } } - private SpannableStringBuilder colorizeFingerprint(String fingerprint) { - SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint); - try { - // for each 4 characters of the fingerprint + 1 space - for (int i = 0; i < fingerprint.length(); i += 5) { - int spanEnd = Math.min(i + 4, fingerprint.length()); - String fourChars = fingerprint.substring(i, spanEnd); - - int raw = Integer.parseInt(fourChars, 16); - byte[] bytes = {(byte) ((raw >> 8) & 0xff - 128), (byte) (raw & 0xff - 128)}; - int[] color = OtherHelper.getRgbForData(bytes); - int r = color[0]; - int g = color[1]; - int b = color[2]; - - // we cannot change black by multiplication, so adjust it to an almost-black grey, - // which will then be brightened to the minimal brightness level - if (r == 0 && g == 0 && b == 0) { - r = 1; - g = 1; - b = 1; - } - - // Convert rgb to brightness - double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; - - // If a color is too dark to be seen on black, - // then brighten it up to a minimal brightness. - if (brightness < 80) { - double factor = 80.0 / brightness; - r = Math.min(255, (int) (r * factor)); - g = Math.min(255, (int) (g * factor)); - b = Math.min(255, (int) (b * factor)); - - // If it is too light, then darken it to a respective maximal brightness. - } else if (brightness > 180) { - double factor = 180.0 / brightness; - r = (int) (r * factor); - g = (int) (g * factor); - b = (int) (b * factor); - } - - // Create a foreground color with the 3 digest integers as RGB - // and then converting that int to hex to use as a color - sb.setSpan(new ForegroundColorSpan(Color.rgb(r, g, b)), - i, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE); - } - } catch (Exception e) { - Log.e(Constants.TAG, "Colorization failed", e); - // if anything goes wrong, then just display the fingerprint without colour, - // instead of partially correct colour or wrong colours - return new SpannableStringBuilder(fingerprint); - } - - return sb; - } - /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java index 3ba291c3d..61572b9ce 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java @@ -23,26 +23,48 @@ import android.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; import android.widget.TextView; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import java.util.ArrayList; + public class ViewKeyUserIdsAdapter extends CursorAdapter { private LayoutInflater mInflater; - private int mIndexUserId; + private int mIndexUserId, mIndexRank; - public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags) { + final private ArrayList<Boolean> mCheckStates; + + public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes) { super(context, c, flags); mInflater = LayoutInflater.from(context); + mCheckStates = showCheckBoxes ? new ArrayList<Boolean>() : null; + initIndex(c); } + public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags) { + this(context, c, flags, false); + } @Override public Cursor swapCursor(Cursor newCursor) { initIndex(newCursor); + if(mCheckStates != null) { + mCheckStates.clear(); + if(newCursor != null) { + int count = newCursor.getCount(); + mCheckStates.ensureCapacity(count); + // initialize to true (use case knowledge: we usually want to sign all uids) + for(int i = 0; i < count; i++) + mCheckStates.add(true); + } + } return super.swapCursor(newCursor); } @@ -56,20 +78,67 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter { private void initIndex(Cursor cursor) { if (cursor != null) { mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID); + mIndexRank = cursor.getColumnIndexOrThrow(UserIds.RANK); } } @Override public void bindView(View view, Context context, Cursor cursor) { - String userIdStr = cursor.getString(mIndexUserId); - TextView userId = (TextView) view.findViewById(R.id.userId); - userId.setText(userIdStr); + TextView vRank = (TextView) view.findViewById(R.id.rank); + TextView vUserId = (TextView) view.findViewById(R.id.userId); + TextView vAddress = (TextView) view.findViewById(R.id.address); + + vRank.setText(Integer.toString(cursor.getInt(mIndexRank))); + + String[] userId = PgpKeyHelper.splitUserId(cursor.getString(mIndexUserId)); + if (userId[0] != null) { + vUserId.setText(userId[0]); + } else { + vUserId.setText(R.string.user_id_no_name); + } + vAddress.setText(userId[1]); + + // don't care further if checkboxes aren't shown + if(mCheckStates == null) + return; + + final CheckBox vCheckBox = (CheckBox) view.findViewById(R.id.checkBox); + final int position = cursor.getPosition(); + vCheckBox.setClickable(false); + vCheckBox.setChecked(mCheckStates.get(position)); + vCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + mCheckStates.set(position, b); + } + }); + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + vCheckBox.toggle(); + } + }); + + } + + public ArrayList<String> getSelectedUserIds() { + ArrayList<String> result = new ArrayList<String>(); + for(int i = 0; i < mCheckStates.size(); i++) { + if(mCheckStates.get(i)) { + mCursor.moveToPosition(i); + result.add(mCursor.getString(mIndexUserId)); + } + } + return result; } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.view_key_userids_item, null); + View view = mInflater.inflate(R.layout.view_key_userids_item, null); + // only need to do this once ever, since mShowCheckBoxes is final + view.findViewById(R.id.checkBox).setVisibility(mCheckStates != null ? View.VISIBLE : View.GONE); + return view; } } |