diff options
27 files changed, 549 insertions, 875 deletions
diff --git a/OpenPGP-Keychain/src/main/AndroidManifest.xml b/OpenPGP-Keychain/src/main/AndroidManifest.xml index 4fabf7432..b1fbcf555 100644 --- a/OpenPGP-Keychain/src/main/AndroidManifest.xml +++ b/OpenPGP-Keychain/src/main/AndroidManifest.xml @@ -61,7 +61,7 @@ android:theme="@style/KeychainTheme" android:label="@string/app_name"> <activity - android:name=".ui.KeyListPublicActivity" + android:name=".ui.KeyListActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/app_name" android:launchMode="singleTop"> @@ -72,12 +72,6 @@ </intent-filter> </activity> <activity - android:name=".ui.KeyListSecretActivity" - android:configChanges="orientation|screenSize|keyboardHidden|keyboard" - android:label="@string/title_manage_secret_keys" - android:launchMode="singleTop"> - </activity> - <activity android:name=".ui.EditKeyActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_edit_key" @@ -86,19 +80,19 @@ android:name=".ui.ViewKeyActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_key_details" - android:parentActivityName=".ui.KeyListPublicActivity"> + android:parentActivityName=".ui.KeyListActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" - android:value=".ui.KeyListPublicActivity" /> + android:value=".ui.KeyListActivity" /> </activity> <activity android:name=".ui.ViewKeyActivityJB" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_key_details" - android:parentActivityName=".ui.KeyListPublicActivity"> + android:parentActivityName=".ui.KeyListActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" - android:value=".ui.KeyListPublicActivity" /> + android:value=".ui.KeyListActivity" /> </activity> <activity android:name=".ui.SelectPublicKeyActivity" @@ -438,4 +432,4 @@ <!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> --> </application> -</manifest>
\ No newline at end of file +</manifest> diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index d8de30b37..d9356951e 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -100,6 +100,10 @@ public class KeychainContract { /** Use if a single item is returned */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key_ring"; + public static Uri buildUnifiedKeyRingsUri() { + return CONTENT_URI; + } + public static Uri buildPublicKeyRingsUri() { return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build(); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 3b60ffc87..1a131a689 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -81,6 +81,8 @@ public class KeychainProvider extends ContentProvider { private static final int API_APPS_BY_ROW_ID = 302; private static final int API_APPS_BY_PACKAGE_NAME = 303; + private static final int UNIFIED_KEY_RING = 401; + // private static final int DATA_STREAM = 401; protected UriMatcher mUriMatcher; @@ -95,6 +97,15 @@ public class KeychainProvider extends ContentProvider { String authority = KeychainContract.CONTENT_AUTHORITY; /** + * unified key rings + * + * <pre> + * key_rings + * </pre> + */ + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS, UNIFIED_KEY_RING); + + /** * public key rings * * <pre> @@ -365,6 +376,10 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID); + // type attribute is special: if there is any grouping, choose secret over public type + projectionMap.put(KeyRingsColumns.TYPE, + "MAX(" + Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + ") AS " + KeyRingsColumns.TYPE); + return projectionMap; } @@ -399,9 +414,11 @@ public class KeychainProvider extends ContentProvider { * Builds default query for keyRings: KeyRings table is joined with UserIds and Keys */ private SQLiteQueryBuilder buildKeyRingQuery(SQLiteQueryBuilder qb, int match) { - // public or secret keyring - qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = "); - qb.appendWhereEscapeString(Integer.toString(getKeyType(match))); + if(match != UNIFIED_KEY_RING) { + // public or secret keyring + qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = "); + qb.appendWhereEscapeString(Integer.toString(getKeyType(match))); + } // join keyrings with keys and userIds // Only get user id and key with rank 0 (main user id and main key) @@ -455,7 +472,22 @@ public class KeychainProvider extends ContentProvider { int match = mUriMatcher.match(uri); + // all query() parameters, for good measure + String groupBy = null, having = null; + switch (match) { + case UNIFIED_KEY_RING: + qb = buildKeyRingQuery(qb, match); + + // GROUP BY so we don't get duplicates + groupBy = Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID; + + if (TextUtils.isEmpty(sortOrder)) { + sortOrder = KeyRings.TYPE + " DESC, " + Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC"; + } + + break; + case PUBLIC_KEY_RING: case SECRET_KEY_RING: qb = buildKeyRingQuery(qb, match); @@ -630,7 +662,7 @@ public class KeychainProvider extends ContentProvider { orderBy = sortOrder; } - Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy); + Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy); // Tell the cursor what uri to watch, so it knows when its source data changes c.setNotificationUri(getContext().getContentResolver(), uri); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index dd538fbf4..1c05344d6 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -483,13 +483,13 @@ public class ProviderHelper { */ public static boolean getSecretMasterKeyCanSign(Context context, long keyRingRowId) { Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId)); - return getMasterKeyCanSign(context, queryUri, keyRingRowId); + return getMasterKeyCanSign(context, queryUri); } /** * Private helper method to get master key private empty status of keyring by its row id */ - private static boolean getMasterKeyCanSign(Context context, Uri queryUri, long keyRingRowId) { + public static boolean getMasterKeyCanSign(Context context, Uri queryUri) { String[] projection = new String[]{ KeyRings.MASTER_KEY_ID, "(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS @@ -515,6 +515,12 @@ public class ProviderHelper { return (masterKeyId > 0); } + public static boolean hasSecretKeyByMasterKeyId(Context context, long masterKeyId) { + Uri queryUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId)); + // see if we can get our master key id back from the uri + return getMasterKeyId(context, queryUri) == masterKeyId; + } + /** * Get master key id of keyring by its row id */ @@ -546,6 +552,26 @@ public class ProviderHelper { return masterKeyId; } + public static long getRowId(Context context, Uri queryUri) { + String[] projection = new String[]{KeyRings._ID}; + Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null); + + long rowId = 0; + try { + if (cursor != null && cursor.moveToFirst()) { + int idCol = cursor.getColumnIndexOrThrow(KeyRings._ID); + + rowId = cursor.getLong(idCol); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + return rowId; + } + /** * Get fingerprint of key */ diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java index 08ca262c3..ebb520197 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java @@ -50,9 +50,9 @@ public class DrawerActivity extends ActionBarActivity { private CharSequence mDrawerTitle; private CharSequence mTitle; - private static Class[] mItemsClass = new Class[] { KeyListPublicActivity.class, + private static Class[] mItemsClass = new Class[] { KeyListActivity.class, EncryptActivity.class, DecryptActivity.class, ImportKeysActivity.class, - KeyListSecretActivity.class, RegisteredAppsListActivity.class }; + RegisteredAppsListActivity.class }; private Class mSelectedItem; private static final int MENU_ID_PREFERENCE = 222; @@ -72,7 +72,6 @@ public class DrawerActivity extends ActionBarActivity { new NavItem("fa-lock", getString(R.string.nav_encrypt)), new NavItem("fa-unlock", getString(R.string.nav_decrypt)), new NavItem("fa-download", getString(R.string.nav_import)), - new NavItem("fa-key", getString(R.string.nav_secret_keys)), new NavItem("fa-android", getString(R.string.nav_apps)) }; mDrawerList.setAdapter(new NavigationDrawerAdapter(this, R.layout.drawer_list_item, diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index 72dc97ccd..0c35bb2b1 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -266,12 +266,10 @@ public class EditKeyActivity extends ActionBarActivity { } else { Log.d(Constants.TAG, "uri: " + mDataUri); - long keyRingRowId = Long.valueOf(mDataUri.getLastPathSegment()); - // get master key id using row id - long masterKeyId = ProviderHelper.getSecretMasterKeyId(this, keyRingRowId); + long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri); - masterCanSign = ProviderHelper.getSecretMasterKeyCanSign(this, keyRingRowId); + masterCanSign = ProviderHelper.getMasterKeyCanSign(this, mDataUri); finallyEdit(masterKeyId, masterCanSign); } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index 9bd9ee225..9eebbed64 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -27,7 +27,7 @@ import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; -public class KeyListPublicActivity extends DrawerActivity { +public class KeyListActivity extends DrawerActivity { ExportHelper mExportHelper; @@ -37,7 +37,7 @@ public class KeyListPublicActivity extends DrawerActivity { mExportHelper = new ExportHelper(this); - setContentView(R.layout.key_list_public_activity); + setContentView(R.layout.key_list_activity); // now setup navigation drawer in DrawerActivity... setupDrawerNavigation(savedInstanceState); @@ -46,19 +46,20 @@ public class KeyListPublicActivity extends DrawerActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.key_list_public, menu); + getMenuInflater().inflate(R.menu.key_list, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.menu_key_list_public_import: + case R.id.menu_key_list_import: Intent intentImport = new Intent(this, ImportKeysActivity.class); startActivityForResult(intentImport, 0); return true; - case R.id.menu_key_list_public_export: + case R.id.menu_key_list_export: + // TODO fix this for unified keylist mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB); return true; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index de965daa5..ea5c60c53 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -17,26 +17,36 @@ package org.sufficientlysecure.keychain.ui; +import java.util.HashMap; import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.adapter.KeyListPublicAdapter; +import org.sufficientlysecure.keychain.ui.adapter.HighlightQueryCursorAdapter; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; +import org.sufficientlysecure.keychain.util.Log; import se.emilsjolander.stickylistheaders.ApiLevelTooLowException; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; +import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.content.Context; import android.content.Intent; import android.database.Cursor; +import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -47,10 +57,13 @@ 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.support.v4.widget.CursorAdapter; import android.support.v4.view.MenuItemCompat; import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.SearchView; +import android.text.Spannable; import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; @@ -62,7 +75,9 @@ import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; +import android.widget.Button; import android.widget.ListView; +import android.widget.TextView; import android.widget.Toast; import com.beardedhen.androidbootstrap.BootstrapButton; @@ -71,10 +86,10 @@ import com.beardedhen.androidbootstrap.BootstrapButton; * Public key list with sticky list headers. It does _not_ extend ListFragment because it uses * StickyListHeaders library which does not extend upon ListView. */ -public class KeyListPublicFragment extends Fragment implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, +public class KeyListFragment extends Fragment implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor> { - private KeyListPublicAdapter mAdapter; + private KeyListAdapter mAdapter; private StickyListHeadersListView mStickyList; // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 @@ -94,9 +109,9 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.key_list_public_fragment, container, false); + View root = inflater.inflate(R.layout.key_list_fragment, container, false); - mStickyList = (StickyListHeadersListView) root.findViewById(R.id.key_list_public_list); + mStickyList = (StickyListHeadersListView) root.findViewById(R.id.key_list_list); mStickyList.setOnItemClickListener(this); @@ -125,8 +140,8 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer }); // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 - mListContainer = root.findViewById(R.id.key_list_public_list_container); - mProgressContainer = root.findViewById(R.id.key_list_public_progress_container); + mListContainer = root.findViewById(R.id.key_list_list_container); + mProgressContainer = root.findViewById(R.id.key_list_progress_container); mListShown = true; return root; @@ -140,6 +155,7 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + mStickyList.setOnItemClickListener(this); mStickyList.setAreHeadersSticky(true); mStickyList.setDrawingListUnderStickyHeader(false); mStickyList.setFastScrollEnabled(true); @@ -149,7 +165,7 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer } // this view is made visible if no data is available - mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_public_empty)); + mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty)); /* * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only @@ -162,7 +178,7 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { android.view.MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.key_list_public_multi, menu); + inflater.inflate(R.menu.key_list_multi, menu); return true; } @@ -173,25 +189,30 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // get row ids for checked positions as long array - long[] ids = mStickyList.getCheckedItemIds(); + + // get IDs for checked positions as long array + long[] ids; switch (item.getItemId()) { - case R.id.menu_key_list_public_multi_encrypt: { + case R.id.menu_key_list_multi_encrypt: { + ids = mAdapter.getCurrentSelectedMasterKeyIds(); encrypt(mode, ids); break; } - case R.id.menu_key_list_public_multi_export: { - ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity()); - mExportHelper.showExportKeysDialog(ids, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB); + case R.id.menu_key_list_multi_delete: { + ids = mStickyList.getWrappedList().getCheckedItemIds(); + showDeleteKeyDialog(mode, ids); break; } - case R.id.menu_key_list_public_multi_delete: { - showDeleteKeyDialog(mode, ids); + case R.id.menu_key_list_multi_export: { + // todo: public/secret needs to be handled differently here + ids = mStickyList.getWrappedList().getCheckedItemIds(); + ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity()); + mExportHelper.showExportKeysDialog(ids, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB); break; } - case R.id.menu_key_list_public_multi_select_all: { - //Select all + case R.id.menu_key_list_multi_select_all: { + // select all for (int i = 0; i < mStickyList.getCount(); i++) { mStickyList.setItemChecked(i, true); } @@ -231,7 +252,7 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer setListShown(false); // Create an empty adapter we will use to display the loaded data. - mAdapter = new KeyListPublicAdapter(getActivity(), null, Id.type.public_key, USER_ID_INDEX); + mAdapter = new KeyListAdapter(getActivity(), null, Id.type.public_key); mStickyList.setAdapter(mAdapter); // Prepare the loader. Either re-connect with an existing one, @@ -242,20 +263,25 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer // These are the rows that we will retrieve. static final String[] PROJECTION = new String[]{ KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.TYPE, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.UserIds.USER_ID, KeychainContract.Keys.IS_REVOKED }; - static final int USER_ID_INDEX = 2; - - static final String SORT_ORDER = UserIds.USER_ID + " ASC"; + static final int INDEX_TYPE = 1; + static final int INDEX_UID = 3; + static final String SORT_ORDER = + // show secret before public key + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings.TYPE + " DESC, " + // sort by user id otherwise + + UserIds.USER_ID + " ASC"; @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. - Uri baseUri = KeyRings.buildPublicKeyRingsUri(); + Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); String where = null; String whereArgs[] = null; if (mCurQuery != null) { @@ -273,6 +299,7 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer // old cursor once we return.) mAdapter.setSearchQuery(mCurQuery); mAdapter.swapCursor(data); + mStickyList.setAdapter(mAdapter); // NOTE: Not supported by StickyListHeader, but reimplemented here @@ -303,21 +330,15 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer } else { viewIntent = new Intent(getActivity(), ViewKeyActivityJB.class); } - viewIntent.setData(KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(id))); + viewIntent.setData(KeychainContract.KeyRings.buildPublicKeyRingsByMasterKeyIdUri(Long.toString(mAdapter.getMasterKeyId(position)))); startActivity(viewIntent); } @TargetApi(11) - public void encrypt(ActionMode mode, long[] keyRingRowIds) { - // get master key ids from row ids - long[] keyRingIds = new long[keyRingRowIds.length]; - for (int i = 0; i < keyRingRowIds.length; i++) { - keyRingIds[i] = ProviderHelper.getPublicMasterKeyId(getActivity(), keyRingRowIds[i]); - } - + protected void encrypt(ActionMode mode, long[] keyRingMasterKeyIds) { Intent intent = new Intent(getActivity(), EncryptActivity.class); intent.setAction(EncryptActivity.ACTION_ENCRYPT); - intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, keyRingIds); + intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, keyRingMasterKeyIds); // used instead of startActivity set actionbar based on callingPackage startActivityForResult(intent, 0); @@ -330,6 +351,7 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer * @param keyRingRowIds */ @TargetApi(11) + // TODO: this method needs an overhaul to handle both public and secret keys gracefully! public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) { // Message is received after key is deleted Handler returnHandler = new Handler() { @@ -368,7 +390,7 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { // Get the searchview - MenuItem searchItem = menu.findItem(R.id.menu_key_list_public_search); + MenuItem searchItem = menu.findItem(R.id.menu_key_list_search); mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem); // Execute this when searching @@ -385,7 +407,7 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer public boolean onMenuItemActionCollapse(MenuItem item) { mCurQuery = null; mSearchView.setQuery("", true); - getLoaderManager().restartLoader(0, null, KeyListPublicFragment.this); + getLoaderManager().restartLoader(0, null, KeyListFragment.this); return true; } }); @@ -444,4 +466,263 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer public void setListShownNoAnimation(boolean shown) { setListShown(shown, false); } + + /** + * Implements StickyListHeadersAdapter from library + */ + private class KeyListAdapter extends HighlightQueryCursorAdapter implements StickyListHeadersAdapter { + private LayoutInflater mInflater; + private int mIndexUserId; + private int mIndexIsRevoked; + private int mMasterKeyId; + + private String mCurQuery; + + @SuppressLint("UseSparseArrays") + private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>(); + + public KeyListAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + + mInflater = LayoutInflater.from(context); + initIndex(c); + } + + @Override + public Cursor swapCursor(Cursor newCursor) { + initIndex(newCursor); + + return super.swapCursor(newCursor); + } + + /** + * Get column indexes for performance reasons just once in constructor and swapCursor. For a + * performance comparison see http://stackoverflow.com/a/17999582 + * + * @param cursor + */ + private void initIndex(Cursor cursor) { + if (cursor != null) { + mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.USER_ID); + mIndexIsRevoked = cursor.getColumnIndexOrThrow(KeychainContract.Keys.IS_REVOKED); + mMasterKeyId = cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.MASTER_KEY_ID); + } + } + + /** + * Bind cursor data to the item list view + * <p/> + * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. Thus + * no ViewHolder is required here. + */ + @Override + public void bindView(View view, Context context, Cursor cursor) { + + { // set name and stuff, common to both key types + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + + String userId = cursor.getString(mIndexUserId); + String[] userIdSplit = PgpKeyHelper.splitUserId(userId); + if (userIdSplit[0] != null) { + mainUserId.setText(highlightSearchQuery(userIdSplit[0])); + } else { + mainUserId.setText(R.string.user_id_no_name); + } + if (userIdSplit[1] != null) { + mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1])); + mainUserIdRest.setVisibility(View.VISIBLE); + } else { + mainUserIdRest.setVisibility(View.GONE); + } + } + + { // set edit button and revoked info, specific by key type + Button button = (Button) view.findViewById(R.id.edit); + TextView revoked = (TextView) view.findViewById(R.id.revoked); + + if(cursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) { + // this is a secret key - show the edit button + revoked.setVisibility(View.GONE); + button.setVisibility(View.VISIBLE); + + final long id = cursor.getLong(mMasterKeyId); + button.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); + editIntent.setData(KeychainContract.KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(id))); + editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); + startActivityForResult(editIntent, 0); + } + }); + } else { + // this is a public key - hide the edit button, show if it's revoked + button.setVisibility(View.GONE); + + boolean isRevoked = cursor.getInt(mIndexIsRevoked) > 0; + revoked.setVisibility(isRevoked ? View.VISIBLE : View.GONE); + } + } + + } + + public long getMasterKeyId(int id) { + + if (!mCursor.moveToPosition(id)) { + throw new IllegalStateException("couldn't move cursor to position " + id); + } + + return mCursor.getLong(mMasterKeyId); + + } + + public int getKeyType(int position) { + + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("couldn't move cursor to position " + position); + } + + return mCursor.getInt(KeyListFragment.INDEX_TYPE); + + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.key_list_item, parent, false); + } + + /** + * Creates a new header view and binds the section headers to it. It uses the ViewHolder + * pattern. Most functionality is similar to getView() from Android's CursorAdapter. + * <p/> + * NOTE: The variables mDataValid and mCursor are available due to the super class + * CursorAdapter. + */ + @Override + public View getHeaderView(int position, View convertView, ViewGroup parent) { + HeaderViewHolder holder; + if (convertView == null) { + holder = new HeaderViewHolder(); + convertView = mInflater.inflate(R.layout.key_list_header, parent, false); + holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text); + holder.count = (TextView) convertView.findViewById(R.id.contacts_num); + convertView.setTag(holder); + } else { + holder = (HeaderViewHolder) convertView.getTag(); + } + + if (!mDataValid) { + // no data available at this point + Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); + return convertView; + } + + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("couldn't move cursor to position " + position); + } + + if(mCursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) { + { // set contact count + int num = mCursor.getCount(); + String contactsTotal = getResources().getQuantityString(R.plurals.n_contacts, num, num); + holder.count.setText(contactsTotal); + holder.count.setVisibility(View.VISIBLE); + } + + holder.text.setText(convertView.getResources().getString(R.string.my_keys)); + return convertView; + } + + // set header text as first char in user id + String userId = mCursor.getString(KeyListFragment.INDEX_UID); + String headerText = convertView.getResources().getString(R.string.user_id_no_name); + if (userId != null && userId.length() > 0) { + headerText = "" + mCursor.getString(KeyListFragment.INDEX_UID).subSequence(0, 1).charAt(0); + } + holder.text.setText(headerText); + holder.count.setVisibility(View.GONE); + return convertView; + } + + /** + * Header IDs should be static, position=1 should always return the same Id that is. + */ + @Override + public long getHeaderId(int position) { + if (!mDataValid) { + // no data available at this point + Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); + return -1; + } + + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("couldn't move cursor to position " + position); + } + + // early breakout: all secret keys are assigned id 0 + if(mCursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) + return 1L; + + // otherwise, return the first character of the name as ID + String userId = mCursor.getString(KeyListFragment.INDEX_UID); + if (userId != null && userId.length() > 0) { + return userId.charAt(0); + } else { + return Long.MAX_VALUE; + } + } + + class HeaderViewHolder { + TextView text; + TextView count; + } + + /** + * -------------------------- MULTI-SELECTION METHODS -------------- + */ + public void setNewSelection(int position, boolean value) { + mSelection.put(position, value); + notifyDataSetChanged(); + } + + public long[] getCurrentSelectedMasterKeyIds() { + long[] ids = new long[mSelection.size()]; + int i = 0; + // get master key ids + for (int pos : mSelection.keySet()) + ids[i++] = mAdapter.getMasterKeyId(pos); + return ids; + } + + public void removeSelection(int position) { + mSelection.remove(position); + notifyDataSetChanged(); + } + + public void clearSelection() { + mSelection.clear(); + 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 + */ + // default color + if (mSelection.get(position) != null) { + // this is a selected position, change color! + v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis)); + } else { + v.setBackgroundColor(Color.TRANSPARENT); + } + + return v; + } + + } + } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java deleted file mode 100644 index cd3c8b4fd..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> - * - * 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; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ExportHelper; - -import android.content.Intent; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; - -public class KeyListSecretActivity extends DrawerActivity { - - ExportHelper mExportHelper; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mExportHelper = new ExportHelper(this); - - setContentView(R.layout.key_list_secret_activity); - - // now setup navigation drawer in DrawerActivity... - setupDrawerNavigation(savedInstanceState); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.key_list_secret, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_key_list_secret_create: - createKey(); - - return true; - case R.id.menu_key_list_secret_create_expert: - createKeyExpert(); - - return true; - case R.id.menu_key_list_secret_export: - mExportHelper.showExportKeysDialog(null, Id.type.secret_key, Constants.path.APP_DIR_FILE_SEC); - - return true; - case R.id.menu_key_list_secret_import: - Intent intentImportFromFile = new Intent(this, ImportKeysActivity.class); - intentImportFromFile.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE); - startActivityForResult(intentImportFromFile, 0); - - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - private void createKey() { - Intent intent = new Intent(this, EditKeyActivity.class); - intent.setAction(EditKeyActivity.ACTION_CREATE_KEY); - intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true); - intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view - startActivityForResult(intent, 0); - } - - private void createKeyExpert() { - Intent intent = new Intent(this, EditKeyActivity.class); - intent.setAction(EditKeyActivity.ACTION_CREATE_KEY); - startActivityForResult(intent, 0); - } - -} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java deleted file mode 100644 index e84b2f4c8..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> - * - * 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; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ExportHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; -import org.sufficientlysecure.keychain.ui.adapter.KeyListSecretAdapter; -import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.support.v4.app.ListFragment; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.support.v7.app.ActionBarActivity; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; -import android.widget.AbsListView.MultiChoiceModeListener; -import android.widget.AdapterView.OnItemClickListener; - -public class KeyListSecretFragment extends ListFragment implements - LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener { - - private KeyListSecretAdapter mAdapter; - - /** - * Define Adapter and Loader on create of Activity - */ - @SuppressLint("NewApi") - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - getListView().setOnItemClickListener(this); - - // Give some text to display if there is no data. In a real - // application this would come from a resource. - setEmptyText(getString(R.string.list_empty)); - - /* - * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only - * available for Android >= 3.0 - */ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); - getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() { - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - android.view.MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.key_list_secret_multi, menu); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // get row ids for checked positions as long array - long[] ids = getListView().getCheckedItemIds(); - - switch (item.getItemId()) { - case R.id.menu_key_list_public_multi_delete: { - showDeleteKeyDialog(mode, ids); - break; - } - case R.id.menu_key_list_public_multi_select_all: { - //Select all - for (int i = 0; i < getListView().getCount(); i++) { - getListView().setItemChecked(i, true); - } - break; - } - case R.id.menu_key_list_public_multi_export: { - ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity()); - mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.path.APP_DIR_FILE_SEC); - break; - } - - } - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - mAdapter.clearSelection(); - } - - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { - if (checked) { - mAdapter.setNewSelection(position, checked); - } else { - mAdapter.removeSelection(position); - } - - int count = getListView().getCheckedItemCount(); - String keysSelected = getResources().getQuantityString( - R.plurals.key_list_selected_keys, count, count); - mode.setTitle(keysSelected); - } - - }); - } - - // We have a menu item to show in action bar. - setHasOptionsMenu(true); - - // Start out with a progress indicator. - setListShown(false); - - // Create an empty adapter we will use to display the loaded data. - mAdapter = new KeyListSecretAdapter(getActivity(), null, 0); - setListAdapter(mAdapter); - - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - 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, - UserIds.USER_ID}; - static final String SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC"; - - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - // This is called when a new Loader needs to be created. This - // sample only has one Loader, so we don't care about the ID. - // First, pick the base URI to use depending on whether we are - // currently filtering. - Uri baseUri = KeyRings.buildSecretKeyRingsUri(); - - // 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, null, null, SORT_ORDER); - } - - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - mAdapter.swapCursor(data); - - // The list should now be shown. - if (isResumed()) { - setListShown(true); - } else { - setListShownNoAnimation(true); - } - } - - public void onLoaderReset(Loader<Cursor> loader) { - // 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. - mAdapter.swapCursor(null); - } - - /** - * On click on item, start key view activity - */ - @Override - public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) { - Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); - editIntent.setData(KeychainContract.KeyRings.buildSecretKeyRingsUri(Long.toString(id))); - editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); - startActivityForResult(editIntent, 0); - } - - /** - * Show dialog to delete key - * - * @param keyRingRowIds - */ - @TargetApi(11) - public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) { - // Message is received after key is deleted - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { - mode.finish(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, - keyRingRowIds, Id.type.secret_key); - - deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); - } - -} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 4cc5e1b62..581d14a22 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; @@ -83,7 +84,13 @@ public class ViewKeyActivity extends ActionBarActivity { selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB); } - mDataUri = getIntent().getData(); + { + // normalize mDataUri to a "by row id" query, to ensure it works with any + // given valid /public/ query + long rowId = ProviderHelper.getRowId(this, getIntent().getData()); + // TODO: handle (rowId == 0) with something else than a crash + mDataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)) ; + } Bundle mainBundle = new Bundle(); mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri); @@ -107,7 +114,7 @@ public class ViewKeyActivity extends ActionBarActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: - Intent homeIntent = new Intent(this, KeyListPublicActivity.class); + Intent homeIntent = new Intent(this, KeyListActivity.class); homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(homeIntent); return true; 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 d223a259f..95c0f7f3a 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 @@ -67,6 +67,8 @@ public class ViewKeyMainFragment extends Fragment implements private TextView mExpiry; private TextView mCreation; private TextView mFingerprint; + private TextView mSecretKey; + private BootstrapButton mActionEdit; private BootstrapButton mActionEncrypt; private ListView mUserIds; @@ -93,8 +95,10 @@ public class ViewKeyMainFragment extends Fragment implements mCreation = (TextView) view.findViewById(R.id.creation); mExpiry = (TextView) view.findViewById(R.id.expiry); mFingerprint = (TextView) view.findViewById(R.id.fingerprint); + mSecretKey = (TextView) view.findViewById(R.id.secret_key); mUserIds = (ListView) view.findViewById(R.id.user_ids); mKeys = (ListView) view.findViewById(R.id.keys); + mActionEdit = (BootstrapButton) view.findViewById(R.id.action_edit); mActionEncrypt = (BootstrapButton) view.findViewById(R.id.action_encrypt); return view; @@ -124,6 +128,31 @@ public class ViewKeyMainFragment extends Fragment implements Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + { // label whether secret key is available, and edit button if it is + final long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), mDataUri); + if(ProviderHelper.hasSecretKeyByMasterKeyId(getActivity(), masterKeyId)) { + // set this attribute. this is a LITTLE unclean, but we have the info available + // right here, so why not. + mSecretKey.setTextColor(getResources().getColor(R.color.emphasis)); + mSecretKey.setText(R.string.secret_key_yes); + + // edit button + mActionEdit.setVisibility(View.VISIBLE); + mActionEdit.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); + editIntent.setData(KeychainContract.KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId))); + editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); + startActivityForResult(editIntent, 0); + } + }); + } else { + mSecretKey.setTextColor(Color.BLACK); + mSecretKey.setText(getResources().getString(R.string.secret_key_no)); + mActionEdit.setVisibility(View.GONE); + } + } + mActionEncrypt.setOnClickListener(new View.OnClickListener() { @Override diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java deleted file mode 100644 index d7bb62d01..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> - * - * 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.HashMap; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.util.Log; - -import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Color; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -/** - * Implements StickyListHeadersAdapter from library - */ -public class KeyListPublicAdapter extends HighlightQueryCursorAdapter implements StickyListHeadersAdapter { - private LayoutInflater mInflater; - private int mSectionColumnIndex; - private int mIndexUserId; - private int mIndexIsRevoked; - - @SuppressLint("UseSparseArrays") - private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>(); - - public KeyListPublicAdapter(Context context, Cursor c, int flags, int sectionColumnIndex) { - super(context, c, flags); - mInflater = LayoutInflater.from(context); - mSectionColumnIndex = sectionColumnIndex; - initIndex(c); - } - - @Override - public Cursor swapCursor(Cursor newCursor) { - initIndex(newCursor); - - return super.swapCursor(newCursor); - } - - /** - * Get column indexes for performance reasons just once in constructor and swapCursor. For a - * performance comparison see http://stackoverflow.com/a/17999582 - * - * @param cursor - */ - private void initIndex(Cursor cursor) { - if (cursor != null) { - mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.USER_ID); - mIndexIsRevoked = cursor.getColumnIndexOrThrow(KeychainContract.Keys.IS_REVOKED); - } - } - - /** - * Bind cursor data to the item list view - * <p/> - * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. Thus - * no ViewHolder is required here. - */ - @Override - public void bindView(View view, Context context, Cursor cursor) { - TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - TextView revoked = (TextView) view.findViewById(R.id.revoked); - - String userId = cursor.getString(mIndexUserId); - String[] userIdSplit = PgpKeyHelper.splitUserId(userId); - if (userIdSplit[0] != null) { - mainUserId.setText(highlightSearchQuery(userIdSplit[0])); - } else { - mainUserId.setText(R.string.user_id_no_name); - } - if (userIdSplit[1] != null) { - mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1])); - mainUserIdRest.setVisibility(View.VISIBLE); - } else { - mainUserIdRest.setVisibility(View.GONE); - } - - boolean isRevoked = cursor.getInt(mIndexIsRevoked) > 0; - if (isRevoked) { - revoked.setVisibility(View.VISIBLE); - } else { - revoked.setVisibility(View.GONE); - } - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.key_list_public_item, null); - } - - /** - * Creates a new header view and binds the section headers to it. It uses the ViewHolder - * pattern. Most functionality is similar to getView() from Android's CursorAdapter. - * <p/> - * NOTE: The variables mDataValid and mCursor are available due to the super class - * CursorAdapter. - */ - @Override - public View getHeaderView(int position, View convertView, ViewGroup parent) { - HeaderViewHolder holder; - if (convertView == null) { - holder = new HeaderViewHolder(); - convertView = mInflater.inflate(R.layout.key_list_public_header, parent, false); - holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text); - convertView.setTag(holder); - } else { - holder = (HeaderViewHolder) convertView.getTag(); - } - - if (!mDataValid) { - // no data available at this point - Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); - return convertView; - } - - if (!mCursor.moveToPosition(position)) { - throw new IllegalStateException("couldn't move cursor to position " + position); - } - - // set header text as first char in user id - String userId = mCursor.getString(mSectionColumnIndex); - String headerText = convertView.getResources().getString(R.string.user_id_no_name); - if (userId != null && userId.length() > 0) { - headerText = "" + mCursor.getString(mSectionColumnIndex).subSequence(0, 1).charAt(0); - } - holder.text.setText(headerText); - return convertView; - } - - /** - * Header IDs should be static, position=1 should always return the same Id that is. - */ - @Override - public long getHeaderId(int position) { - if (!mDataValid) { - // no data available at this point - Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); - return -1; - } - - if (!mCursor.moveToPosition(position)) { - throw new IllegalStateException("couldn't move cursor to position " + position); - } - - // return the first character of the name as ID because this is what - // headers are based upon - String userId = mCursor.getString(mSectionColumnIndex); - if (userId != null && userId.length() > 0) { - return userId.subSequence(0, 1).charAt(0); - } else { - return Long.MAX_VALUE; - } - } - - class HeaderViewHolder { - TextView text; - } - - /** - * -------------------------- MULTI-SELECTION METHODS -------------- - */ - public void setNewSelection(int position, boolean value) { - mSelection.put(position, value); - notifyDataSetChanged(); - } - - public void removeSelection(int position) { - mSelection.remove(position); - notifyDataSetChanged(); - } - - public void clearSelection() { - mSelection.clear(); - 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 && mSelection.get(position).booleanValue()) { - // color for selected items - v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis)); - } else { - // default color - v.setBackgroundColor(Color.TRANSPARENT); - } - return v; - } - -} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java deleted file mode 100644 index 0bffcaa19..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> - * - * 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.HashMap; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Color; -import android.support.v4.widget.CursorAdapter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -public class KeyListSecretAdapter extends CursorAdapter { - private LayoutInflater mInflater; - - private int mIndexUserId; - - @SuppressLint("UseSparseArrays") - private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>(); - - public KeyListSecretAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - - mInflater = LayoutInflater.from(context); - initIndex(c); - } - - @Override - public Cursor swapCursor(Cursor newCursor) { - initIndex(newCursor); - - return super.swapCursor(newCursor); - } - - /** - * Get column indexes for performance reasons just once in constructor and swapCursor. For a - * performance comparison see http://stackoverflow.com/a/17999582 - * - * @param cursor - */ - private void initIndex(Cursor cursor) { - if (cursor != null) { - mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID); - } - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - - String userId = cursor.getString(mIndexUserId); - String[] userIdSplit = PgpKeyHelper.splitUserId(userId); - - if (userIdSplit[0] != null) { - mainUserId.setText(userIdSplit[0]); - } else { - mainUserId.setText(R.string.user_id_no_name); - } - if (userIdSplit[1] != null) { - mainUserIdRest.setText(userIdSplit[1]); - } else { - mainUserIdRest.setText(""); - } - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.key_list_secret_item, null); - } - - /** -------------------------- MULTI-SELECTION METHODS -------------- */ - public void setNewSelection(int position, boolean value) { - mSelection.put(position, value); - notifyDataSetChanged(); - } - - public void removeSelection(int position) { - mSelection.remove(position); - notifyDataSetChanged(); - } - - public void clearSelection() { - mSelection.clear(); - 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) { - // color for selected items - v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis)); - } else { - // default color - v.setBackgroundColor(Color.TRANSPARENT); - } - return v; - } - -} diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_public_activity.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_activity.xml index f0e843e56..65d246d7b 100644 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_public_activity.xml +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_activity.xml @@ -9,8 +9,8 @@ android:layout_height="match_parent" > <fragment - android:id="@+id/key_list_public_fragment" - android:name="org.sufficientlysecure.keychain.ui.KeyListPublicFragment" + android:id="@+id/key_list_fragment" + android:name="org.sufficientlysecure.keychain.ui.KeyListFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_public_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_fragment.xml index db82c8771..77bd6f4e9 100644 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_public_fragment.xml +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_fragment.xml @@ -7,7 +7,7 @@ <!--rebuild functionality of ListFragment --> <LinearLayout - android:id="@+id/key_list_public_progress_container" + android:id="@+id/key_list_progress_container" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" @@ -30,12 +30,12 @@ </LinearLayout> <FrameLayout - android:id="@+id/key_list_public_list_container" + android:id="@+id/key_list_list_container" android:layout_width="match_parent" android:layout_height="match_parent"> <se.emilsjolander.stickylistheaders.StickyListHeadersListView - android:id="@+id/key_list_public_list" + android:id="@+id/key_list_list" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" @@ -47,7 +47,7 @@ android:scrollbarStyle="outsideOverlay" /> <LinearLayout - android:id="@+id/key_list_public_empty" + android:id="@+id/key_list_empty" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" @@ -105,4 +105,4 @@ </FrameLayout> -</FrameLayout>
\ No newline at end of file +</FrameLayout> diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_header.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_header.xml new file mode 100644 index 000000000..09ac1c856 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_header.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" > + + <org.sufficientlysecure.keychain.ui.widget.UnderlineTextView + android:id="@+id/stickylist_header_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="start|left" + android:padding="8dp" + android:textColor="@color/emphasis" + android:textSize="17sp" + android:textStyle="bold" + android:text="header text" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="contact count" + android:id="@+id/contacts_num" + android:layout_centerVertical="true" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_marginRight="10px" + android:visibility="visible" + android:textColor="@android:color/darker_gray" /> + +</RelativeLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_public_item.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_item.xml index f07d60214..f52693138 100644 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_public_item.xml +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_item.xml @@ -3,13 +3,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeight" - android:layout_marginRight="?android:attr/scrollbarSize" android:gravity="center_vertical" android:paddingLeft="8dp" - android:paddingRight="8dp" android:paddingTop="4dp" android:paddingBottom="4dp" - android:singleLine="true"> + android:singleLine="true" + android:descendantFocusability="blocksDescendants" + android:focusable="false"> <TextView android:id="@+id/mainUserId" @@ -17,6 +17,7 @@ android:layout_height="wrap_content" android:text="@string/label_main_user_id" android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" /> @@ -30,6 +31,23 @@ android:layout_alignParentLeft="true" android:layout_alignParentStart="true" /> + <Button + style="@android:style/Widget.DeviceDefault.Button.Borderless.Small" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/edit" + android:focusable="false" + android:layout_alignTop="@+id/mainUserId" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_alignParentBottom="false" + android:layout_alignParentTop="false" + android:layout_alignBottom="@+id/mainUserIdRest" + android:visibility="visible" + android:enabled="true" + android:textColor="@color/black" + android:text="@string/edit" /> + <TextView android:id="@+id/revoked" android:layout_width="wrap_content" @@ -37,9 +55,9 @@ android:textAppearance="?android:attr/textAppearanceSmall" android:text="@string/revoked" android:textColor="#e00" - android:visibility="gone" + android:visibility="visible" android:layout_alignParentTop="true" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" /> -</RelativeLayout>
\ No newline at end of file +</RelativeLayout> diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_public_header.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_public_header.xml deleted file mode 100644 index 5768e4153..000000000 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_public_header.xml +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" > - - <org.sufficientlysecure.keychain.ui.widget.UnderlineTextView - android:id="@+id/stickylist_header_text" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="start|left" - android:padding="8dp" - android:textColor="@color/emphasis" - android:textSize="17sp" - android:textStyle="bold" /> - -</RelativeLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_secret_activity.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_secret_activity.xml deleted file mode 100644 index cd208a545..000000000 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_secret_activity.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/drawer_layout" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <FrameLayout - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <fragment - android:id="@+id/key_list_secret_fragment" - android:name="org.sufficientlysecure.keychain.ui.KeyListSecretFragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingBottom="16dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:scrollbarStyle="outsideOverlay" /> - </FrameLayout> - - <include layout="@layout/drawer_list" /> - -</android.support.v4.widget.DrawerLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_secret_item.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_secret_item.xml deleted file mode 100644 index 7d5492265..000000000 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_secret_item.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeight" - android:gravity="center_vertical" - android:paddingLeft="8dp" - android:paddingTop="4dp" - android:paddingBottom="4dp" - android:singleLine="true"> - - <TextView - android:id="@+id/mainUserId" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/label_main_user_id" - android:textAppearance="?android:attr/textAppearanceMedium" - android:layout_alignParentTop="true" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" /> - - <TextView - android:id="@+id/mainUserIdRest" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="<user@example.com>" - android:textAppearance="?android:attr/textAppearanceSmall" - android:layout_below="@+id/mainUserId" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" /> - -</RelativeLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml index b44ca82ec..3c8a4270b 100644 --- a/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml +++ b/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml @@ -160,7 +160,10 @@ android:layout_height="wrap_content" /> </TableRow> - <TableRow> + <TableRow + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:id="@+id/tableRow"> <TextView android:layout_width="wrap_content" @@ -175,6 +178,22 @@ android:layout_height="wrap_content" android:typeface="monospace" /> </TableRow> + + <TableRow> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:paddingRight="10dip" + android:text="@string/label_secret_key" /> + + <TextView + android:id="@+id/secret_key" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:typeface="monospace" /> + </TableRow> </TableLayout> <TextView @@ -212,6 +231,17 @@ android:text="@string/section_actions" /> <com.beardedhen.androidbootstrap.BootstrapButton + android:id="@+id/action_edit" + android:layout_width="match_parent" + android:layout_height="60dp" + android:padding="4dp" + android:layout_marginBottom="10dp" + android:text="@string/key_view_action_edit" + bootstrapbutton:bb_icon_left="fa-key" + bootstrapbutton:bb_type="info" + android:visibility="gone" /> + + <com.beardedhen.androidbootstrap.BootstrapButton android:id="@+id/action_encrypt" android:layout_width="match_parent" android:layout_height="60dp" diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_public.xml b/OpenPGP-Keychain/src/main/res/menu/key_list.xml index 35f4fca92..addef0c2d 100644 --- a/OpenPGP-Keychain/src/main/res/menu/key_list_public.xml +++ b/OpenPGP-Keychain/src/main/res/menu/key_list.xml @@ -3,18 +3,18 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> <item - android:id="@+id/menu_key_list_public_import" + android:id="@+id/menu_key_list_import" app:showAsAction="ifRoom|withText" android:icon="@drawable/ic_action_add_person" android:title="@string/menu_import" /> <item - android:id="@+id/menu_key_list_public_export" + android:id="@+id/menu_key_list_export" app:showAsAction="never" android:title="@string/menu_export_keys" /> <item - android:id="@+id/menu_key_list_public_search" + android:id="@+id/menu_key_list_search" android:title="@string/menu_search" android:icon="@drawable/ic_action_search" app:actionViewClass="android.support.v7.widget.SearchView" app:showAsAction="collapseActionView|ifRoom" /> -</menu>
\ No newline at end of file +</menu> diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_public_multi.xml b/OpenPGP-Keychain/src/main/res/menu/key_list_multi.xml index 9df17615e..db709052f 100644 --- a/OpenPGP-Keychain/src/main/res/menu/key_list_public_multi.xml +++ b/OpenPGP-Keychain/src/main/res/menu/key_list_multi.xml @@ -2,20 +2,20 @@ <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item - android:id="@+id/menu_key_list_public_multi_select_all" + android:id="@+id/menu_key_list_multi_select_all" android:icon="@drawable/ic_action_select_all" android:title="@string/menu_select_all" /> <item - android:id="@+id/menu_key_list_public_multi_export" + android:id="@+id/menu_key_list_multi_export" android:icon="@drawable/ic_action_import_export" android:title="@string/menu_export_key" /> <item - android:id="@+id/menu_key_list_public_multi_encrypt" + android:id="@+id/menu_key_list_multi_encrypt" android:icon="@drawable/ic_action_secure" android:title="@string/menu_encrypt_to" /> <item - android:id="@+id/menu_key_list_public_multi_delete" + android:id="@+id/menu_key_list_multi_delete" android:icon="@drawable/ic_action_discard" android:title="@string/menu_delete_key" /> -</menu>
\ No newline at end of file +</menu> diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_secret.xml b/OpenPGP-Keychain/src/main/res/menu/key_list_secret.xml deleted file mode 100644 index ce4999229..000000000 --- a/OpenPGP-Keychain/src/main/res/menu/key_list_secret.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto"> - - <item - android:id="@+id/menu_key_list_secret_create" - app:showAsAction="ifRoom|withText" - android:title="@string/menu_create_key" /> - <item - android:id="@+id/menu_key_list_secret_create_expert" - app:showAsAction="never" - android:title="@string/menu_create_key_expert" /> - <item - android:id="@+id/menu_key_list_secret_import" - app:showAsAction="never" - android:title="@string/menu_import" /> - <item - android:id="@+id/menu_key_list_secret_export" - app:showAsAction="never" - android:title="@string/menu_export_keys" /> - -</menu>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_secret_multi.xml b/OpenPGP-Keychain/src/main/res/menu/key_list_secret_multi.xml deleted file mode 100644 index e7e46815e..000000000 --- a/OpenPGP-Keychain/src/main/res/menu/key_list_secret_multi.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android"> - - <item - android:id="@+id/menu_key_list_public_multi_select_all" - android:icon="@drawable/ic_action_select_all" - android:title="@string/menu_select_all" /> - <item - android:id="@+id/menu_key_list_public_multi_export" - android:icon="@drawable/ic_action_import_export" - android:title="@string/menu_export_key" /> - <item - android:id="@+id/menu_key_list_public_multi_delete" - android:icon="@drawable/ic_action_discard" - android:title="@string/menu_delete_key" /> - -</menu>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/values/strings.xml b/OpenPGP-Keychain/src/main/res/values/strings.xml index 07f402802..1c7ead403 100644 --- a/OpenPGP-Keychain/src/main/res/values/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values/strings.xml @@ -155,6 +155,11 @@ <string name="revoked">revoked</string> <string name="user_id">User ID</string> + <plurals name="n_contacts"> + <item quantity="one">1 contact</item> + <item quantity="other">%d contacts</item> + </plurals> + <plurals name="n_key_servers"> <item quantity="one">%d keyserver</item> <item quantity="other">%d keyservers</item> @@ -438,6 +443,7 @@ <string name="key_list_empty_button_import">importing keys.</string> <!-- Key view --> + <string name="key_view_action_edit">Edit this key</string> <string name="key_view_action_encrypt">Encrypt to this contact</string> <string name="key_view_action_certify">Certify this contact\'s key</string> <string name="key_view_tab_main">Info</string> @@ -452,5 +458,10 @@ <string name="nav_apps">Registered Apps</string> <string name="drawer_open">Open navigation drawer</string> <string name="drawer_close">Close navigation drawer</string> + <string name="edit">Edit</string> + <string name="my_keys">My Keys</string> + <string name="label_secret_key">Secret Key</string> + <string name="secret_key_yes">available</string> + <string name="secret_key_no">unavailable</string> </resources> |