diff options
33 files changed, 783 insertions, 1067 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..fdc4b7bda 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/HelpActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java index ac8250bef..48068a6c4 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2012-2014 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 @@ -17,8 +17,6 @@ package org.sufficientlysecure.keychain.ui; -import java.util.ArrayList; - import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter; @@ -29,7 +27,7 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; public class HelpActivity extends ActionBarActivity { - public static final String EXTRA_SELECTED_TAB = "selectedTab"; + public static final String EXTRA_SELECTED_TAB = "selected_tab"; ViewPager mViewPager; TabsAdapter mTabsAdapter; @@ -59,19 +57,24 @@ public class HelpActivity extends ActionBarActivity { Bundle startBundle = new Bundle(); startBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_start); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)), - HelpHtmlFragment.class, startBundle, (selectedTab == 0) ); + HelpHtmlFragment.class, startBundle, (selectedTab == 0)); + + Bundle faqBundle = new Bundle(); + faqBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_faq); + mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_faq)), + HelpHtmlFragment.class, faqBundle, (selectedTab == 1)); Bundle nfcBundle = new Bundle(); nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_nfc_beam); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)), - HelpHtmlFragment.class, nfcBundle, (selectedTab == 1) ); + HelpHtmlFragment.class, nfcBundle, (selectedTab == 2)); Bundle changelogBundle = new Bundle(); changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_changelog); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)), - HelpHtmlFragment.class, changelogBundle, (selectedTab == 2) ); + HelpHtmlFragment.class, changelogBundle, (selectedTab == 3)); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)), - HelpAboutFragment.class, null, (selectedTab == 3) ); + HelpAboutFragment.class, null, (selectedTab == 4)); } }
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index cd3c8b4fd..684ee6959 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2012-2014 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 @@ -27,7 +27,7 @@ import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; -public class KeyListSecretActivity extends DrawerActivity { +public class KeyListActivity extends DrawerActivity { ExportHelper mExportHelper; @@ -37,7 +37,7 @@ public class KeyListSecretActivity extends DrawerActivity { mExportHelper = new ExportHelper(this); - setContentView(R.layout.key_list_secret_activity); + setContentView(R.layout.key_list_activity); // now setup navigation drawer in DrawerActivity... setupDrawerNavigation(savedInstanceState); @@ -46,33 +46,37 @@ public class KeyListSecretActivity extends DrawerActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.key_list_secret, 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_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); + 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_export: + // TODO fix this for unified keylist + mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB); + + return true; + case R.id.menu_key_list_create: + createKey(); + + return true; + case R.id.menu_key_list_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); + + + default: + return super.onOptionsItemSelected(item); } } 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..70ffe968d 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2013-2014 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 @@ -17,26 +17,33 @@ package org.sufficientlysecure.keychain.ui; +import java.util.HashMap; import java.util.ArrayList; 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.ProviderHelper; -import org.sufficientlysecure.keychain.ui.adapter.KeyListPublicAdapter; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; +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; @@ -62,7 +69,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 +80,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 +103,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 +134,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 +149,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 +159,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 +172,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 +183,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 +246,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 +257,28 @@ 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 int INDEX_TYPE = 1; + static final int INDEX_MASTER_KEY_ID = 2; + static final int INDEX_USER_ID = 3; + static final int INDEX_IS_REVOKED = 4; - static final String SORT_ORDER = UserIds.USER_ID + " ASC"; + 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 +296,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 +327,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 +348,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 +387,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 +404,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 +463,229 @@ 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 HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>(); + + public KeyListAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + + mInflater = LayoutInflater.from(context); + } + + @Override + public Cursor swapCursor(Cursor newCursor) { + return super.swapCursor(newCursor); + } + + /** + * 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(INDEX_USER_ID); + 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(INDEX_MASTER_KEY_ID); + 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(INDEX_IS_REVOKED) > 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(INDEX_MASTER_KEY_ID); + } + + @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_USER_ID); + String headerText = convertView.getResources().getString(R.string.user_id_no_name); + if (userId != null && userId.length() > 0) { + headerText = "" + mCursor.getString(KeyListFragment.INDEX_USER_ID).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_USER_ID); + 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 + */ + if (mSelection.get(position) != null) { + // selected position color + v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis)); + } else { + // default color + v.setBackgroundColor(Color.TRANSPARENT); + } + + return v; + } + + } + } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java deleted file mode 100644 index 9bd9ee225..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2012-2014 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 KeyListPublicActivity extends DrawerActivity { - - ExportHelper mExportHelper; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mExportHelper = new ExportHelper(this); - - setContentView(R.layout.key_list_public_activity); - - // now setup navigation drawer in DrawerActivity... - setupDrawerNavigation(savedInstanceState); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.key_list_public, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_key_list_public_import: - Intent intentImport = new Intent(this, ImportKeysActivity.class); - startActivityForResult(intentImport, 0); - - return true; - case R.id.menu_key_list_public_export: - mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB); - - return true; - default: - return super.onOptionsItemSelected(item); - } - } - -} 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 a43e3f5b5..0e73ad983 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/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java index 32266839c..99cac1152 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java @@ -18,24 +18,7 @@ package org.sufficientlysecure.keychain.util; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLEncoder; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.Locale; - +import android.text.Html; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; @@ -51,7 +34,14 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; -import android.text.Html; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.*; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * TODO: @@ -82,7 +72,7 @@ public class HkpKeyServer extends KeyServer { } private String mHost; - private short mPort = 11371; + private short mPort; // example: // pub 2048R/<a href="/pks/lookup?op=get&search=0x887DF4BE9F5C9090">9F5C9090</a> 2009-08-17 <a @@ -95,9 +85,25 @@ public class HkpKeyServer extends KeyServer { public static Pattern USER_ID_LINE = Pattern.compile("^ +(.+)$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); - public HkpKeyServer(String host) { - mHost = host; - } + private static final short PORT_DEFAULT = 11371; + + /** + * @param hostAndPort may be just "<code>hostname</code>" (eg. "<code>pool.sks-keyservers.net</code>"), then it will + * connect using {@link #PORT_DEFAULT}. However, port may be specified after colon + * ("<code>hostname:port</code>", eg. "<code>p80.pool.sks-keyservers.net:80</code>"). + */ + public HkpKeyServer(String hostAndPort) { + String host = hostAndPort; + short port = PORT_DEFAULT; + final int colonPosition = hostAndPort.lastIndexOf(':'); + if (colonPosition > 0) { + host = hostAndPort.substring(0, colonPosition); + final String portStr = hostAndPort.substring(colonPosition + 1); + port = Short.decode(portStr); + } + mHost = host; + mPort = port; + } public HkpKeyServer(String host, short port) { mHost = host; diff --git a/OpenPGP-Keychain/src/main/res/layout/decrypt_activity.xml b/OpenPGP-Keychain/src/main/res/layout/decrypt_activity.xml index a974f6ec0..25c7c000c 100644 --- a/OpenPGP-Keychain/src/main/res/layout/decrypt_activity.xml +++ b/OpenPGP-Keychain/src/main/res/layout/decrypt_activity.xml @@ -5,201 +5,206 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <ScrollView - android:layout_width="match_parent" - android:layout_height="match_parent" - android:fillViewport="true" - android:orientation="vertical"> + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true" + android:orientation="vertical"> - <LinearLayout + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingTop="4dp" + android:paddingLeft="10dp" + android:paddingRight="10dp"> + + <RelativeLayout + android:id="@+id/signature" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingTop="4dp" + android:clickable="true" + android:orientation="horizontal" + android:padding="4dp" android:paddingLeft="10dp" android:paddingRight="10dp"> <RelativeLayout - android:id="@+id/signature" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:clickable="true" - android:orientation="horizontal" - android:padding="4dp" - android:paddingLeft="10dp" - android:paddingRight="10dp"> + android:id="@+id/relativeLayout"> - <RelativeLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:id="@+id/relativeLayout"> - - <ImageView - android:id="@+id/ic_signature" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/signed_large" /> - - <ImageView - android:id="@+id/ic_signature_status" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/overlay_error" /> - </RelativeLayout> - - <com.beardedhen.androidbootstrap.BootstrapButton - android:id="@+id/lookup_key" - android:visibility="gone" - android:layout_width="wrap_content" - android:layout_height="50dp" - android:padding="4dp" - android:text="@string/btn_lookup_key" - bootstrapbutton:bb_icon_left="fa-download" - bootstrapbutton:bb_type="info" - bootstrapbutton:bb_size="small" - android:layout_alignParentTop="true" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" /> - - <TextView - android:id="@+id/mainUserId" + <ImageView + android:id="@+id/ic_signature" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="left" - android:text="@string/label_main_user_id" - android:textAppearance="?android:attr/textAppearanceMedium" - android:layout_toRightOf="@+id/relativeLayout" /> + android:src="@drawable/signed_large" /> - <TextView - android:id="@+id/mainUserIdRest" + <ImageView + android:id="@+id/ic_signature_status" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="left" - android:text="Main User Id Rest" - android:textAppearance="?android:attr/textAppearanceSmall" - android:layout_below="@+id/mainUserId" - android:layout_toRightOf="@+id/relativeLayout" /> + android:src="@drawable/overlay_error" /> </RelativeLayout> - <LinearLayout - android:layout_width="match_parent" + <com.beardedhen.androidbootstrap.BootstrapButton + android:id="@+id/lookup_key" + android:visibility="gone" + android:layout_width="wrap_content" + android:layout_height="50dp" + android:padding="4dp" + android:text="@string/btn_lookup_key" + bootstrapbutton:bb_icon_left="fa-download" + bootstrapbutton:bb_type="info" + bootstrapbutton:bb_size="small" + android:layout_alignParentTop="true" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" /> + + <TextView + android:id="@+id/mainUserId" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal"> + android:layout_gravity="left" + android:text="@string/label_main_user_id" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_toRightOf="@+id/relativeLayout" /> - <ImageView - android:id="@+id/sourcePrevious" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/ic_previous" /> + <TextView + android:id="@+id/mainUserIdRest" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="left" + android:text="Main User Id Rest" + android:textAppearance="?android:attr/textAppearanceSmall" + android:layout_below="@+id/mainUserId" + android:layout_toRightOf="@+id/relativeLayout" /> + </RelativeLayout> - <TextView - android:id="@+id/sourceLabel" - style="@style/SectionHeader" - android:layout_width="0dip" - android:layout_height="match_parent" - android:layout_weight="1" - android:gravity="center_horizontal|center_vertical" - android:text="@string/label_message" - android:textAppearance="?android:attr/textAppearanceMedium" /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> - <ImageView - android:id="@+id/sourceNext" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/ic_next" /> + <ImageView + android:id="@+id/sourcePrevious" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_previous" /> + + <TextView + android:id="@+id/sourceLabel" + style="@style/SectionHeader" + android:layout_width="0dip" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="center_horizontal|center_vertical" + android:text="@string/label_message" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + <ImageView + android:id="@+id/sourceNext" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_next" /> + </LinearLayout> + + <ViewFlipper + android:id="@+id/source" + android:layout_width="match_parent" + android:layout_height="0dip" + android:layout_weight="1"> + + <LinearLayout + android:id="@+id/sourceMessage" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="4dp"> + + <EditText + android:id="@+id/message" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="top" + android:inputType="text|textCapSentences|textMultiLine|textLongMessage" + android:scrollHorizontally="true" /> </LinearLayout> - <ViewFlipper - android:id="@+id/source" + <LinearLayout + android:id="@+id/sourceFile" android:layout_width="match_parent" - android:layout_height="0dip" - android:layout_weight="1"> + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="4dp"> <LinearLayout - android:id="@+id/sourceMessage" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:padding="4dp"> + android:layout_height="wrap_content" + android:orientation="horizontal"> <EditText - android:id="@+id/message" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="top" - android:inputType="text|textCapSentences|textMultiLine|textLongMessage" - android:scrollHorizontally="true" /> + android:id="@+id/filename" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="top|left" + android:inputType="textMultiLine|textUri" + android:lines="4" + android:maxLines="10" + android:minLines="2" + android:scrollbars="vertical" /> + + <com.beardedhen.androidbootstrap.BootstrapButton + android:id="@+id/btn_browse" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="4dp" + bootstrapbutton:bb_icon_left="fa-folder-open" + bootstrapbutton:bb_roundedCorners="true" + bootstrapbutton:bb_size="default" + bootstrapbutton:bb_type="default" /> </LinearLayout> <LinearLayout - android:id="@+id/sourceFile" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:padding="4dp"> + android:layout_height="wrap_content" + android:orientation="horizontal"> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <EditText - android:id="@+id/filename" - android:layout_width="0dip" - android:layout_height="wrap_content" - android:layout_weight="1" - android:inputType="textNoSuggestions" /> - - <com.beardedhen.androidbootstrap.BootstrapButton - android:id="@+id/btn_browse" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="4dp" - bootstrapbutton:bb_icon_left="fa-folder-open" - bootstrapbutton:bb_roundedCorners="true" - bootstrapbutton:bb_size="default" - bootstrapbutton:bb_type="default" /> - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" + <CheckBox + android:id="@+id/deleteAfterDecryption" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal"> - - <CheckBox - android:id="@+id/deleteAfterDecryption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:text="@string/label_delete_after_decryption" /> - </LinearLayout> + android:layout_gravity="center_vertical" + android:text="@string/label_delete_after_decryption" /> </LinearLayout> - </ViewFlipper> + </LinearLayout> + </ViewFlipper> - <TextView - style="@style/SectionHeader" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="4dp" - android:text="@string/section_decrypt_verify" /> + <TextView + style="@style/SectionHeader" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" + android:text="@string/section_decrypt_verify" /> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:padding="4dp"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="4dp"> - <com.beardedhen.androidbootstrap.BootstrapButton - android:id="@+id/action_decrypt" - android:layout_width="match_parent" - android:layout_height="60dp" - android:padding="4dp" - android:text="@string/btn_decrypt_verify" - bootstrapbutton:bb_icon_left="fa-unlock" - bootstrapbutton:bb_type="info" /> - </LinearLayout> + <com.beardedhen.androidbootstrap.BootstrapButton + android:id="@+id/action_decrypt" + android:layout_width="match_parent" + android:layout_height="60dp" + android:padding="4dp" + android:text="@string/btn_decrypt_verify" + bootstrapbutton:bb_icon_left="fa-unlock" + bootstrapbutton:bb_type="info" /> </LinearLayout> - </ScrollView> + </LinearLayout> + </ScrollView> <include layout="@layout/drawer_list" /> diff --git a/OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml b/OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml index dc4cf0063..4fe65e341 100644 --- a/OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml +++ b/OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml @@ -238,7 +238,12 @@ android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" - android:inputType="textNoSuggestions" /> + android:gravity="top|left" + android:inputType="textMultiLine|textUri" + android:lines="4" + android:maxLines="10" + android:minLines="2" + android:scrollbars="vertical" /> <com.beardedhen.androidbootstrap.BootstrapButton android:id="@+id/btn_browse" @@ -257,6 +262,7 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:clickable="true"> + <com.beardedhen.androidbootstrap.FontAwesomeText android:id="@+id/advancedSettingsIcon" android:layout_width="wrap_content" @@ -265,7 +271,8 @@ android:textSize="12sp" android:paddingTop="@dimen/padding_medium" android:paddingBottom="@dimen/padding_medium" - fontawesometext:fa_icon="fa-chevron-right"/> + fontawesometext:fa_icon="fa-chevron-right" /> + <TextView android:id="@+id/advancedSettings" android:layout_width="wrap_content" @@ -273,7 +280,7 @@ android:text="@string/btn_encryption_advanced_settings_show" android:paddingTop="@dimen/padding_medium" android:paddingBottom="@dimen/padding_medium" - android:textColor="@color/emphasis"/> + android:textColor="@color/emphasis" /> </LinearLayout> <LinearLayout diff --git a/OpenPGP-Keychain/src/main/res/layout/file_dialog.xml b/OpenPGP-Keychain/src/main/res/layout/file_dialog.xml index a2939f571..83d697001 100644 --- a/OpenPGP-Keychain/src/main/res/layout/file_dialog.xml +++ b/OpenPGP-Keychain/src/main/res/layout/file_dialog.xml @@ -26,8 +26,8 @@ android:layout_weight="1" android:gravity="top|left" android:inputType="textMultiLine|textUri" - android:lines="2" - android:maxLines="6" + android:lines="4" + android:maxLines="10" android:minLines="2" android:scrollbars="vertical" /> 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..10223522c 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,36 @@ 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" - 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 + + <item + android:id="@+id/menu_key_list_create" + app:showAsAction="never" + android:title="@string/menu_create_key" /> + + <item + android:id="@+id/menu_key_list_create_expert" + app:showAsAction="never" + android:title="@string/menu_create_key_expert" /> + + <item + android:id="@+id/menu_key_list_export" + app:showAsAction="never" + android:title="@string/menu_export_keys" /> + + <item + android:id="@+id/menu_key_list_secret_export" + app:showAsAction="never" + android:title="@string/menu_export_secret_keys" /> + +</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/raw/help_faq.html b/OpenPGP-Keychain/src/main/res/raw/help_faq.html new file mode 100644 index 000000000..b3d5b3a11 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw/help_faq.html @@ -0,0 +1,12 @@ +<!-- Maintain structure with headings with h2 tags and content with p tags. +This makes it easy to translate the values with transifex! +And don't add newlines before or after p tags because of transifex --> +<html> +<head> +</head> +<body> +<h2>TODO</h2> +<p>text</p> + +</body> +</html> diff --git a/OpenPGP-Keychain/src/main/res/values/strings.xml b/OpenPGP-Keychain/src/main/res/values/strings.xml index 07f402802..77891d6c7 100644 --- a/OpenPGP-Keychain/src/main/res/values/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values/strings.xml @@ -78,6 +78,7 @@ <string name="menu_import">Import</string> <string name="menu_import_from_nfc">Import from NFC</string> <string name="menu_export_keys">Export all keys</string> + <string name="menu_export_secret_keys">Export all secret keys</string> <string name="menu_export_key">Export to file</string> <string name="menu_delete_key">Delete key</string> <string name="menu_create_key">Create key</string> @@ -155,6 +156,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> @@ -368,6 +374,7 @@ <!-- Help --> <string name="help_tab_start">Start</string> + <string name="help_tab_faq">FAQ</string> <string name="help_tab_nfc_beam">NFC Beam</string> <string name="help_tab_changelog">Changelog</string> <string name="help_tab_about">About</string> @@ -438,6 +445,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 +460,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> |