diff options
| author | Vincent Breitmoser <valodim@mugenguild.com> | 2014-03-12 18:05:28 +0100 | 
|---|---|---|
| committer | Vincent Breitmoser <valodim@mugenguild.com> | 2014-03-12 18:05:28 +0100 | 
| commit | 69f5bf6b577234053e700a43a4a7ba721e827c6a (patch) | |
| tree | 65ebb2ab3a40508e3d97c10ac9ca87aa43da7691 | |
| parent | a9e5619a14f5cef4e0211d56c000b0e853120f1d (diff) | |
| parent | be558944b52ac038c40bdf84a3d52c9ae2e7a218 (diff) | |
| download | open-keychain-69f5bf6b577234053e700a43a4a7ba721e827c6a.tar.gz open-keychain-69f5bf6b577234053e700a43a4a7ba721e827c6a.tar.bz2 open-keychain-69f5bf6b577234053e700a43a4a7ba721e827c6a.zip  | |
Merge branch 'unified-keylist'
Conflicts:
	OpenPGP-Keychain/src/main/AndroidManifest.xml
	OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java
	OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java
	OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java
	OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java
	OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java
	OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java
	OpenPGP-Keychain/src/main/res/layout/key_list_secret_item.xml
	OpenPGP-Keychain/src/main/res/menu/key_list.xml
	OpenPGP-Keychain/src/main/res/menu/key_list_multi.xml
	OpenPGP-Keychain/src/main/res/menu/key_list_secret_multi.xml
27 files changed, 600 insertions, 876 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..706b30d05 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 @@ -78,6 +78,7 @@ public class KeychainContract {      public static final String PATH_PUBLIC = "public";      public static final String PATH_SECRET = "secret"; +    public static final String PATH_UNIFIED = "unified";      public static final String PATH_BY_MASTER_KEY_ID = "master_key_id";      public static final String PATH_BY_KEY_ID = "key_id"; @@ -100,6 +101,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.buildUpon().appendPath(PATH_UNIFIED).build(); +        } +          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..8b0efdbbd 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; @@ -227,6 +229,16 @@ public class KeychainProvider extends ContentProvider {                  + KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_APPS_BY_PACKAGE_NAME);          /** +         * unified key rings +         * <pre> +         * +         * key_rings/unified +         * +         */ +        matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" +                + KeychainContract.PATH_UNIFIED, UNIFIED_KEY_RING); + +        /**           * data stream           *           * <pre> @@ -455,6 +467,69 @@ public class KeychainProvider extends ContentProvider {          int match = mUriMatcher.match(uri); +        // screw that switch +        if(match == UNIFIED_KEY_RING) { + +            // join keyrings with keys and userIds +            // Only get user id and key with rank 0 (main user id and main key) +            qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.KEYS + " ON " + "(" +                    + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.KEYS + "." +                    + KeysColumns.KEY_RING_ROW_ID + " AND " + Tables.KEYS + "." +                    + KeysColumns.RANK + " = '0') " + " INNER JOIN " + Tables.USER_IDS + " ON " +                    + "(" + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "." +                    + UserIdsColumns.KEY_RING_ROW_ID + " AND " + Tables.USER_IDS + "." +                    + UserIdsColumns.RANK + " = '0')"); + +            { +                HashMap<String, String> projectionMap = new HashMap<String, String>(); + +                projectionMap.put(KeyRingsColumns.TYPE, "MAX(" + Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + ")"); + +                projectionMap.put(BaseColumns._ID, Tables.KEY_RINGS + "." + BaseColumns._ID); +                projectionMap.put(KeyRingsColumns.KEY_RING_DATA, Tables.KEY_RINGS + "." +                        + KeyRingsColumns.KEY_RING_DATA); +                projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID); +                // TODO: deprecated master key id +                //projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID); + +                projectionMap.put(KeysColumns.FINGERPRINT, Tables.KEYS + "." + KeysColumns.FINGERPRINT); +                projectionMap.put(KeysColumns.IS_REVOKED, Tables.KEYS + "." + KeysColumns.IS_REVOKED); + +                projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID); + +                qb.setProjectionMap(projectionMap); +            } + +            if (TextUtils.isEmpty(sortOrder)) { +                sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC"; +            } + +            // If no sort order is specified use the default +            String orderBy; +            if (TextUtils.isEmpty(sortOrder)) { +                orderBy = Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " DESC"; +            } else { +                orderBy = Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " DESC, " + sortOrder; +            } + +            Cursor c = qb.query(db, projection, selection, selectionArgs, +                    Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID, +                    null, orderBy); + +            // Tell the cursor what uri to watch, so it knows when its source data changes +            c.setNotificationUri(getContext().getContentResolver(), uri); + +            if (Constants.DEBUG) { +                Log.d(Constants.TAG, +                        "Query: " +                                + qb.buildQuery(projection, selection, selectionArgs, Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID, null, +                                orderBy, null)); +                Log.d(Constants.TAG, "Cursor: " + DatabaseUtils.dumpCursorToString(c)); +            } + +            return c; +        } +          switch (match) {              case PUBLIC_KEY_RING:              case SECRET_KEY_RING: diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index dd538fbf4..1c05344d6 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -483,13 +483,13 @@ public class ProviderHelper {       */      public static boolean getSecretMasterKeyCanSign(Context context, long keyRingRowId) {          Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId)); -        return getMasterKeyCanSign(context, queryUri, keyRingRowId); +        return getMasterKeyCanSign(context, queryUri);      }      /**       * Private helper method to get master key private empty status of keyring by its row id       */ -    private static boolean getMasterKeyCanSign(Context context, Uri queryUri, long keyRingRowId) { +    public static boolean getMasterKeyCanSign(Context context, Uri queryUri) {          String[] projection = new String[]{                  KeyRings.MASTER_KEY_ID,                  "(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS @@ -515,6 +515,12 @@ public class ProviderHelper {          return (masterKeyId > 0);      } +    public static boolean hasSecretKeyByMasterKeyId(Context context, long masterKeyId) { +        Uri queryUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId)); +        // see if we can get our master key id back from the uri +        return getMasterKeyId(context, queryUri) == masterKeyId; +    } +      /**       * Get master key id of keyring by its row id       */ @@ -546,6 +552,26 @@ public class ProviderHelper {          return masterKeyId;      } +    public static long getRowId(Context context, Uri queryUri) { +        String[] projection = new String[]{KeyRings._ID}; +        Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null); + +        long rowId = 0; +        try { +            if (cursor != null && cursor.moveToFirst()) { +                int idCol = cursor.getColumnIndexOrThrow(KeyRings._ID); + +                rowId = cursor.getLong(idCol); +            } +        } finally { +            if (cursor != null) { +                cursor.close(); +            } +        } + +        return rowId; +    } +      /**       * Get fingerprint of key       */ diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java index 08ca262c3..ebb520197 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java @@ -50,9 +50,9 @@ public class DrawerActivity extends ActionBarActivity {      private CharSequence mDrawerTitle;      private CharSequence mTitle; -    private static Class[] mItemsClass = new Class[] { KeyListPublicActivity.class, +    private static Class[] mItemsClass = new Class[] { KeyListActivity.class,              EncryptActivity.class, DecryptActivity.class, ImportKeysActivity.class, -            KeyListSecretActivity.class, RegisteredAppsListActivity.class }; +            RegisteredAppsListActivity.class };      private Class mSelectedItem;      private static final int MENU_ID_PREFERENCE = 222; @@ -72,7 +72,6 @@ public class DrawerActivity extends ActionBarActivity {                  new NavItem("fa-lock", getString(R.string.nav_encrypt)),                  new NavItem("fa-unlock", getString(R.string.nav_decrypt)),                  new NavItem("fa-download", getString(R.string.nav_import)), -                new NavItem("fa-key", getString(R.string.nav_secret_keys)),                  new NavItem("fa-android", getString(R.string.nav_apps)) };          mDrawerList.setAdapter(new NavigationDrawerAdapter(this, R.layout.drawer_list_item, diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index 72dc97ccd..0c35bb2b1 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -266,12 +266,10 @@ public class EditKeyActivity extends ActionBarActivity {          } else {              Log.d(Constants.TAG, "uri: " + mDataUri); -            long keyRingRowId = Long.valueOf(mDataUri.getLastPathSegment()); -              // get master key id using row id -            long masterKeyId = ProviderHelper.getSecretMasterKeyId(this, keyRingRowId); +            long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri); -            masterCanSign = ProviderHelper.getSecretMasterKeyCanSign(this, keyRingRowId); +            masterCanSign = ProviderHelper.getMasterKeyCanSign(this, mDataUri);              finallyEdit(masterKeyId, masterCanSign);          }      } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index 9bd9ee225..9eebbed64 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -27,7 +27,7 @@ import android.os.Bundle;  import android.view.Menu;  import android.view.MenuItem; -public class KeyListPublicActivity extends DrawerActivity { +public class KeyListActivity extends DrawerActivity {      ExportHelper mExportHelper; @@ -37,7 +37,7 @@ public class KeyListPublicActivity extends DrawerActivity {          mExportHelper = new ExportHelper(this); -        setContentView(R.layout.key_list_public_activity); +        setContentView(R.layout.key_list_activity);          // now setup navigation drawer in DrawerActivity...          setupDrawerNavigation(savedInstanceState); @@ -46,19 +46,20 @@ public class KeyListPublicActivity extends DrawerActivity {      @Override      public boolean onCreateOptionsMenu(Menu menu) {          super.onCreateOptionsMenu(menu); -        getMenuInflater().inflate(R.menu.key_list_public, menu); +        getMenuInflater().inflate(R.menu.key_list, menu);          return true;      }      @Override      public boolean onOptionsItemSelected(MenuItem item) {          switch (item.getItemId()) { -        case R.id.menu_key_list_public_import: +        case R.id.menu_key_list_import:              Intent intentImport = new Intent(this, ImportKeysActivity.class);              startActivityForResult(intentImport, 0);              return true; -        case R.id.menu_key_list_public_export: +        case R.id.menu_key_list_export: +            // TODO fix this for unified keylist              mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB);              return true; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index de965daa5..5b57132d4 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -17,26 +17,34 @@  package org.sufficientlysecure.keychain.ui; +import java.util.HashMap;  import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.Id;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;  import org.sufficientlysecure.keychain.helper.ExportHelper;  import org.sufficientlysecure.keychain.provider.KeychainContract;  import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;  import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;  import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.adapter.KeyListPublicAdapter;  import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; +import org.sufficientlysecure.keychain.util.Log;  import se.emilsjolander.stickylistheaders.ApiLevelTooLowException;  import se.emilsjolander.stickylistheaders.StickyListHeadersListView; +import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;  import android.annotation.SuppressLint;  import android.annotation.TargetApi; +import android.content.Context;  import android.content.Intent;  import android.database.Cursor; +import android.graphics.Color;  import android.net.Uri;  import android.os.Build;  import android.os.Bundle; @@ -47,10 +55,13 @@ import android.support.v4.app.Fragment;  import android.support.v4.app.LoaderManager;  import android.support.v4.content.CursorLoader;  import android.support.v4.content.Loader; +import android.support.v4.widget.CursorAdapter;  import android.support.v4.view.MenuItemCompat;  import android.support.v7.app.ActionBarActivity;  import android.support.v7.widget.SearchView; +import android.text.Spannable;  import android.text.TextUtils; +import android.text.style.ForegroundColorSpan;  import android.view.ActionMode;  import android.view.LayoutInflater;  import android.view.Menu; @@ -62,7 +73,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 +84,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 +107,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 +138,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 +153,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 +163,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 +176,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,30 +187,21 @@ 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); -                            break; -                        } -                        case R.id.menu_key_list_public_multi_delete: { +                        case R.id.menu_key_list_multi_delete: { +                            ids = mStickyList.getWrappedList().getCheckedItemIds();                              showDeleteKeyDialog(mode, ids);                              break;                          } -                        case R.id.menu_key_list_public_multi_select_all: { -                            //Select all -                            for (int i = 0; i < mStickyList.getCount(); i++) { -                                mStickyList.setItemChecked(i, true); -                            } -                            break; -                        }                      }                      return true;                  } @@ -231,7 +236,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 +247,21 @@ 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_UID = 3;      static final String SORT_ORDER = 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 +279,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 +310,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 +331,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 +370,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 +387,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 +446,289 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer      public void setListShownNoAnimation(boolean shown) {          setListShown(shown, false);      } + +    /** +     * Implements StickyListHeadersAdapter from library +     */ +    private class KeyListAdapter extends CursorAdapter implements StickyListHeadersAdapter { +        private LayoutInflater mInflater; +        private int mIndexUserId; +        private int mIndexIsRevoked; +        private int mMasterKeyId; + +        private String mCurQuery; + +        @SuppressLint("UseSparseArrays") +        private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>(); + +        public KeyListAdapter(Context context, Cursor c, int flags) { +            super(context, c, flags); + +            mInflater = LayoutInflater.from(context); +            initIndex(c); +        } + +        @Override +        public Cursor swapCursor(Cursor newCursor) { +            initIndex(newCursor); + +            return super.swapCursor(newCursor); +        } + +        /** +         * Get column indexes for performance reasons just once in constructor and swapCursor. For a +         * performance comparison see http://stackoverflow.com/a/17999582 +         * +         * @param cursor +         */ +        private void initIndex(Cursor cursor) { +            if (cursor != null) { +                mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.USER_ID); +                mIndexIsRevoked = cursor.getColumnIndexOrThrow(KeychainContract.Keys.IS_REVOKED); +                mMasterKeyId = cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.MASTER_KEY_ID); +            } +        } + +        /** +         * Bind cursor data to the item list view +         * <p/> +         * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. Thus +         * no ViewHolder is required here. +         */ +        @Override +        public void bindView(View view, Context context, Cursor cursor) { + +            { // set name and stuff, common to both key types +                TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); +                TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + +                String userId = cursor.getString(mIndexUserId); +                String[] userIdSplit = PgpKeyHelper.splitUserId(userId); +                if (userIdSplit[0] != null) { +            mainUserId.setText(highlightSearchQuery(userIdSplit[0])); +                } else { +                    mainUserId.setText(R.string.user_id_no_name); +                } +                if (userIdSplit[1] != null) { +            mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1])); +                    mainUserIdRest.setVisibility(View.VISIBLE); +                } else { +                    mainUserIdRest.setVisibility(View.GONE); +                } +            } + +            { // set edit button and revoked info, specific by key type +                Button button = (Button) view.findViewById(R.id.edit); +                TextView revoked = (TextView) view.findViewById(R.id.revoked); + +                if(cursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) { +                    // this is a secret key - show the edit button +                    revoked.setVisibility(View.GONE); +                    button.setVisibility(View.VISIBLE); + +                    final long id = cursor.getLong(mMasterKeyId); +                    button.setOnClickListener(new OnClickListener() { +                        public void onClick(View view) { +                            Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); +                            editIntent.setData(KeychainContract.KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(id))); +                            editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); +                            startActivityForResult(editIntent, 0); +                        } +                    }); +                } else { +                    // this is a public key - hide the edit button, show if it's revoked +                    button.setVisibility(View.GONE); + +                    boolean isRevoked = cursor.getInt(mIndexIsRevoked) > 0; +                    revoked.setVisibility(isRevoked ? View.VISIBLE : View.GONE); +                } +            } + +        } + +        public long getMasterKeyId(int id) { + +            if (!mCursor.moveToPosition(id)) { +                throw new IllegalStateException("couldn't move cursor to position " + id); +            } + +            return mCursor.getLong(mMasterKeyId); + +        } + +        public int getKeyType(int position) { + +            if (!mCursor.moveToPosition(position)) { +                throw new IllegalStateException("couldn't move cursor to position " + position); +            } + +            return mCursor.getInt(KeyListFragment.INDEX_TYPE); + +        } + +        @Override +        public View newView(Context context, Cursor cursor, ViewGroup parent) { +            return mInflater.inflate(R.layout.key_list_item, parent, false); +        } + +        /** +         * Creates a new header view and binds the section headers to it. It uses the ViewHolder +         * pattern. Most functionality is similar to getView() from Android's CursorAdapter. +         * <p/> +         * NOTE: The variables mDataValid and mCursor are available due to the super class +         * CursorAdapter. +         */ +        @Override +        public View getHeaderView(int position, View convertView, ViewGroup parent) { +            HeaderViewHolder holder; +            if (convertView == null) { +                holder = new HeaderViewHolder(); +                convertView = mInflater.inflate(R.layout.key_list_header, parent, false); +                holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text); +                holder.count = (TextView) convertView.findViewById(R.id.contacts_num); +                convertView.setTag(holder); +            } else { +                holder = (HeaderViewHolder) convertView.getTag(); +            } + +            if (!mDataValid) { +                // no data available at this point +                Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); +                return convertView; +            } + +            if (!mCursor.moveToPosition(position)) { +                throw new IllegalStateException("couldn't move cursor to position " + position); +            } + +            if(mCursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) { +                { // set contact count +                    int num = mCursor.getCount(); +                    String contactsTotal = getResources().getQuantityString(R.plurals.n_contacts, num, num); +                    holder.count.setText(contactsTotal); +                    holder.count.setVisibility(View.VISIBLE); +                } + +                holder.text.setText(convertView.getResources().getString(R.string.my_keys)); +                return convertView; +            } + +            // set header text as first char in user id +            String userId = mCursor.getString(KeyListFragment.INDEX_UID); +            String headerText = convertView.getResources().getString(R.string.user_id_no_name); +            if (userId != null && userId.length() > 0) { +                headerText = "" + mCursor.getString(KeyListFragment.INDEX_UID).subSequence(0, 1).charAt(0); +            } +            holder.text.setText(headerText); +            holder.count.setVisibility(View.GONE); +            return convertView; +        } + +        /** +         * Header IDs should be static, position=1 should always return the same Id that is. +         */ +        @Override +        public long getHeaderId(int position) { +            if (!mDataValid) { +                // no data available at this point +                Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); +                return -1; +            } + +            if (!mCursor.moveToPosition(position)) { +                throw new IllegalStateException("couldn't move cursor to position " + position); +            } + +            // early breakout: all secret keys are assigned id 0 +            if(mCursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) +                return 1L; + +            // otherwise, return the first character of the name as ID +            String userId = mCursor.getString(KeyListFragment.INDEX_UID); +            if (userId != null && userId.length() > 0) { +                return userId.charAt(0); +            } else { +                return Long.MAX_VALUE; +            } +        } + +        class HeaderViewHolder { +            TextView text; +            TextView count; +        } + +        /** +         * -------------------------- MULTI-SELECTION METHODS -------------- +         */ +        public void setNewSelection(int position, boolean value) { +            mSelection.put(position, value); +            notifyDataSetChanged(); +        } + +        public long[] getCurrentSelectedMasterKeyIds() { +            long[] ids = new long[mSelection.size()]; +            int i = 0; +            // get master key ids +            for (int pos : mSelection.keySet()) +                ids[i++] = mAdapter.getMasterKeyId(pos); +            return ids; +        } + +        public void removeSelection(int position) { +            mSelection.remove(position); +            notifyDataSetChanged(); +        } + +        public void clearSelection() { +            mSelection.clear(); +            notifyDataSetChanged(); +        } + +        @Override +        public View getView(int position, View convertView, ViewGroup parent) { +            // let the adapter handle setting up the row views +            View v = super.getView(position, convertView, parent); + +            /** +             * Change color for multi-selection +             */ +            // default color +            v.setBackgroundColor(Color.TRANSPARENT); +            if (mSelection.get(position) != null) { +                // this is a selected position, change color! +                v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis)); +            } +            return v; +        } + +        // search highlight methods + +        public void setSearchQuery(String searchQuery) { +            mCurQuery = searchQuery; +        } + +        public String getSearchQuery() { +            return mCurQuery; +        } + +        protected Spannable highlightSearchQuery(String text) { +            Spannable highlight = Spannable.Factory.getInstance().newSpannable(text); + +            if (mCurQuery != null) { +                Pattern pattern = Pattern.compile("(?i)" + mCurQuery); +                Matcher matcher = pattern.matcher(text); +                if (matcher.find()) { +                    highlight.setSpan( +                            new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)), +                            matcher.start(), +                            matcher.end(), +                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); +                } +                return highlight; +            } else { +                return highlight; +            } +        } +    } +  } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java deleted file mode 100644 index cd3c8b4fd..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program.  If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ExportHelper; - -import android.content.Intent; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; - -public class KeyListSecretActivity extends DrawerActivity { - -    ExportHelper mExportHelper; - -    @Override -    public void onCreate(Bundle savedInstanceState) { -        super.onCreate(savedInstanceState); - -        mExportHelper = new ExportHelper(this); - -        setContentView(R.layout.key_list_secret_activity); - -        // now setup navigation drawer in DrawerActivity... -        setupDrawerNavigation(savedInstanceState); -    } - -    @Override -    public boolean onCreateOptionsMenu(Menu menu) { -        super.onCreateOptionsMenu(menu); -        getMenuInflater().inflate(R.menu.key_list_secret, menu); -        return true; -    } - -    @Override -    public boolean onOptionsItemSelected(MenuItem item) { -        switch (item.getItemId()) { -        case R.id.menu_key_list_secret_create: -            createKey(); - -            return true; -        case R.id.menu_key_list_secret_create_expert: -            createKeyExpert(); - -            return true; -        case R.id.menu_key_list_secret_export: -            mExportHelper.showExportKeysDialog(null, Id.type.secret_key, Constants.path.APP_DIR_FILE_SEC); - -            return true; -        case R.id.menu_key_list_secret_import: -            Intent intentImportFromFile = new Intent(this, ImportKeysActivity.class); -            intentImportFromFile.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE); -            startActivityForResult(intentImportFromFile, 0); - -            return true; -        default: -            return super.onOptionsItemSelected(item); -        } -    } - -    private void createKey() { -        Intent intent = new Intent(this, EditKeyActivity.class); -        intent.setAction(EditKeyActivity.ACTION_CREATE_KEY); -        intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true); -        intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view -        startActivityForResult(intent, 0); -    } - -    private void createKeyExpert() { -        Intent intent = new Intent(this, EditKeyActivity.class); -        intent.setAction(EditKeyActivity.ACTION_CREATE_KEY); -        startActivityForResult(intent, 0); -    } - -} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java deleted file mode 100644 index e84b2f4c8..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program.  If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ExportHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; -import org.sufficientlysecure.keychain.ui.adapter.KeyListSecretAdapter; -import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.support.v4.app.ListFragment; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.support.v7.app.ActionBarActivity; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; -import android.widget.AbsListView.MultiChoiceModeListener; -import android.widget.AdapterView.OnItemClickListener; - -public class KeyListSecretFragment extends ListFragment implements -        LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener { - -    private KeyListSecretAdapter mAdapter; - -    /** -     * Define Adapter and Loader on create of Activity -     */ -    @SuppressLint("NewApi") -    @Override -    public void onActivityCreated(Bundle savedInstanceState) { -        super.onActivityCreated(savedInstanceState); - -        getListView().setOnItemClickListener(this); - -        // Give some text to display if there is no data. In a real -        // application this would come from a resource. -        setEmptyText(getString(R.string.list_empty)); - -        /* -         * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only -         * available for Android >= 3.0 -         */ -        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { -            getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); -            getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() { - -                @Override -                public boolean onCreateActionMode(ActionMode mode, Menu menu) { -                    android.view.MenuInflater inflater = getActivity().getMenuInflater(); -                    inflater.inflate(R.menu.key_list_secret_multi, menu); -                    return true; -                } - -                @Override -                public boolean onPrepareActionMode(ActionMode mode, Menu menu) { -                    return false; -                } - -                @Override -                public boolean onActionItemClicked(ActionMode mode, MenuItem item) { -                    // get row ids for checked positions as long array -                    long[] ids = getListView().getCheckedItemIds(); - -                    switch (item.getItemId()) { -                        case R.id.menu_key_list_public_multi_delete: { -                            showDeleteKeyDialog(mode, ids); -                            break; -                        } -                        case R.id.menu_key_list_public_multi_select_all: { -                            //Select all -                            for (int i = 0; i < getListView().getCount(); i++) { -                                getListView().setItemChecked(i, true); -                            } -                            break; -                        } -                        case R.id.menu_key_list_public_multi_export: { -                            ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity()); -                            mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.path.APP_DIR_FILE_SEC); -                            break; -                        } - -                    } -                    return true; -                } - -                @Override -                public void onDestroyActionMode(ActionMode mode) { -                    mAdapter.clearSelection(); -                } - -                @Override -                public void onItemCheckedStateChanged(ActionMode mode, int position, long id, -                                                      boolean checked) { -                    if (checked) { -                        mAdapter.setNewSelection(position, checked); -                    } else { -                        mAdapter.removeSelection(position); -                    } - -                    int count = getListView().getCheckedItemCount(); -                    String keysSelected = getResources().getQuantityString( -                            R.plurals.key_list_selected_keys, count, count); -                    mode.setTitle(keysSelected); -                } - -            }); -        } - -        // We have a menu item to show in action bar. -        setHasOptionsMenu(true); - -        // Start out with a progress indicator. -        setListShown(false); - -        // Create an empty adapter we will use to display the loaded data. -        mAdapter = new KeyListSecretAdapter(getActivity(), null, 0); -        setListAdapter(mAdapter); - -        // Prepare the loader. Either re-connect with an existing one, -        // or start a new one. -        getLoaderManager().initLoader(0, null, this); -    } - -    // These are the rows that we will retrieve. -    static final String[] PROJECTION = new String[]{KeyRings._ID, KeyRings.MASTER_KEY_ID, -            UserIds.USER_ID}; -    static final String SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC"; - -    public Loader<Cursor> onCreateLoader(int id, Bundle args) { -        // This is called when a new Loader needs to be created. This -        // sample only has one Loader, so we don't care about the ID. -        // First, pick the base URI to use depending on whether we are -        // currently filtering. -        Uri baseUri = KeyRings.buildSecretKeyRingsUri(); - -        // Now create and return a CursorLoader that will take care of -        // creating a Cursor for the data being displayed. -        return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, SORT_ORDER); -    } - -    public void onLoadFinished(Loader<Cursor> loader, Cursor data) { -        // Swap the new cursor in. (The framework will take care of closing the -        // old cursor once we return.) -        mAdapter.swapCursor(data); - -        // The list should now be shown. -        if (isResumed()) { -            setListShown(true); -        } else { -            setListShownNoAnimation(true); -        } -    } - -    public void onLoaderReset(Loader<Cursor> loader) { -        // This is called when the last Cursor provided to onLoadFinished() -        // above is about to be closed. We need to make sure we are no -        // longer using it. -        mAdapter.swapCursor(null); -    } - -    /** -     * On click on item, start key view activity -     */ -    @Override -    public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) { -        Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); -        editIntent.setData(KeychainContract.KeyRings.buildSecretKeyRingsUri(Long.toString(id))); -        editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); -        startActivityForResult(editIntent, 0); -    } - -    /** -     * Show dialog to delete key -     * -     * @param keyRingRowIds -     */ -    @TargetApi(11) -    public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) { -        // Message is received after key is deleted -        Handler returnHandler = new Handler() { -            @Override -            public void handleMessage(Message message) { -                if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { -                    mode.finish(); -                } -            } -        }; - -        // Create a new Messenger for the communication back -        Messenger messenger = new Messenger(returnHandler); - -        DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, -                keyRingRowIds, Id.type.secret_key); - -        deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); -    } - -} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 4cc5e1b62..581d14a22 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;  import org.sufficientlysecure.keychain.helper.ExportHelper;  import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract;  import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter;  import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; @@ -83,7 +84,13 @@ public class ViewKeyActivity extends ActionBarActivity {              selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);          } -        mDataUri = getIntent().getData(); +        { +            // normalize mDataUri to a "by row id" query, to ensure it works with any +            // given valid /public/ query +            long rowId = ProviderHelper.getRowId(this, getIntent().getData()); +            // TODO: handle (rowId == 0) with something else than a crash +            mDataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)) ; +        }          Bundle mainBundle = new Bundle();          mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri); @@ -107,7 +114,7 @@ public class ViewKeyActivity extends ActionBarActivity {      public boolean onOptionsItemSelected(MenuItem item) {          switch (item.getItemId()) {              case android.R.id.home: -                Intent homeIntent = new Intent(this, KeyListPublicActivity.class); +                Intent homeIntent = new Intent(this, KeyListActivity.class);                  homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);                  startActivity(homeIntent);                  return true; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java index adb06a068..4950126dd 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 @@ -63,6 +63,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; @@ -89,8 +91,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; @@ -120,6 +124,31 @@ public class ViewKeyMainFragment extends Fragment  implements          Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); +        { // label whether secret key is available, and edit button if it is +            final long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), mDataUri); +            if(ProviderHelper.hasSecretKeyByMasterKeyId(getActivity(), masterKeyId)) { +                // set this attribute. this is a LITTLE unclean, but we have the info available +                // right here, so why not. +                mSecretKey.setTextColor(getResources().getColor(R.color.emphasis)); +                mSecretKey.setText(R.string.secret_key_yes); + +                // edit button +                mActionEdit.setVisibility(View.VISIBLE); +                mActionEdit.setOnClickListener(new View.OnClickListener() { +                    public void onClick(View view) { +                        Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); +                        editIntent.setData(KeychainContract.KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId))); +                        editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); +                        startActivityForResult(editIntent, 0); +                    } +                }); +            } else { +                mSecretKey.setTextColor(Color.BLACK); +                mSecretKey.setText(getResources().getString(R.string.secret_key_no)); +                mActionEdit.setVisibility(View.GONE); +            } +        } +          mActionEncrypt.setOnClickListener(new View.OnClickListener() {              @Override diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java deleted file mode 100644 index d7bb62d01..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program.  If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui.adapter; - -import java.util.HashMap; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.util.Log; - -import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Color; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -/** - * Implements StickyListHeadersAdapter from library - */ -public class KeyListPublicAdapter extends HighlightQueryCursorAdapter implements StickyListHeadersAdapter { -    private LayoutInflater mInflater; -    private int mSectionColumnIndex; -    private int mIndexUserId; -    private int mIndexIsRevoked; - -    @SuppressLint("UseSparseArrays") -    private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>(); - -    public KeyListPublicAdapter(Context context, Cursor c, int flags, int sectionColumnIndex) { -        super(context, c, flags); -        mInflater = LayoutInflater.from(context); -        mSectionColumnIndex = sectionColumnIndex; -        initIndex(c); -    } - -    @Override -    public Cursor swapCursor(Cursor newCursor) { -        initIndex(newCursor); - -        return super.swapCursor(newCursor); -    } - -    /** -     * Get column indexes for performance reasons just once in constructor and swapCursor. For a -     * performance comparison see http://stackoverflow.com/a/17999582 -     * -     * @param cursor -     */ -    private void initIndex(Cursor cursor) { -        if (cursor != null) { -            mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.USER_ID); -            mIndexIsRevoked = cursor.getColumnIndexOrThrow(KeychainContract.Keys.IS_REVOKED); -        } -    } - -    /** -     * Bind cursor data to the item list view -     * <p/> -     * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. Thus -     * no ViewHolder is required here. -     */ -    @Override -    public void bindView(View view, Context context, Cursor cursor) { -        TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); -        TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); -        TextView revoked = (TextView) view.findViewById(R.id.revoked); - -        String userId = cursor.getString(mIndexUserId); -        String[] userIdSplit = PgpKeyHelper.splitUserId(userId); -        if (userIdSplit[0] != null) { -            mainUserId.setText(highlightSearchQuery(userIdSplit[0])); -        } else { -            mainUserId.setText(R.string.user_id_no_name); -        } -        if (userIdSplit[1] != null) { -            mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1])); -            mainUserIdRest.setVisibility(View.VISIBLE); -        } else { -            mainUserIdRest.setVisibility(View.GONE); -        } - -        boolean isRevoked = cursor.getInt(mIndexIsRevoked) > 0; -        if (isRevoked) { -            revoked.setVisibility(View.VISIBLE); -        } else { -            revoked.setVisibility(View.GONE); -        } -    } - -    @Override -    public View newView(Context context, Cursor cursor, ViewGroup parent) { -        return mInflater.inflate(R.layout.key_list_public_item, null); -    } - -    /** -     * Creates a new header view and binds the section headers to it. It uses the ViewHolder -     * pattern. Most functionality is similar to getView() from Android's CursorAdapter. -     * <p/> -     * NOTE: The variables mDataValid and mCursor are available due to the super class -     * CursorAdapter. -     */ -    @Override -    public View getHeaderView(int position, View convertView, ViewGroup parent) { -        HeaderViewHolder holder; -        if (convertView == null) { -            holder = new HeaderViewHolder(); -            convertView = mInflater.inflate(R.layout.key_list_public_header, parent, false); -            holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text); -            convertView.setTag(holder); -        } else { -            holder = (HeaderViewHolder) convertView.getTag(); -        } - -        if (!mDataValid) { -            // no data available at this point -            Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); -            return convertView; -        } - -        if (!mCursor.moveToPosition(position)) { -            throw new IllegalStateException("couldn't move cursor to position " + position); -        } - -        // set header text as first char in user id -        String userId = mCursor.getString(mSectionColumnIndex); -        String headerText = convertView.getResources().getString(R.string.user_id_no_name); -        if (userId != null && userId.length() > 0) { -            headerText = "" + mCursor.getString(mSectionColumnIndex).subSequence(0, 1).charAt(0); -        } -        holder.text.setText(headerText); -        return convertView; -    } - -    /** -     * Header IDs should be static, position=1 should always return the same Id that is. -     */ -    @Override -    public long getHeaderId(int position) { -        if (!mDataValid) { -            // no data available at this point -            Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); -            return -1; -        } - -        if (!mCursor.moveToPosition(position)) { -            throw new IllegalStateException("couldn't move cursor to position " + position); -        } - -        // return the first character of the name as ID because this is what -        // headers are based upon -        String userId = mCursor.getString(mSectionColumnIndex); -        if (userId != null && userId.length() > 0) { -            return userId.subSequence(0, 1).charAt(0); -        } else { -            return Long.MAX_VALUE; -        } -    } - -    class HeaderViewHolder { -        TextView text; -    } - -    /** -     * -------------------------- MULTI-SELECTION METHODS -------------- -     */ -    public void setNewSelection(int position, boolean value) { -        mSelection.put(position, value); -        notifyDataSetChanged(); -    } - -    public void removeSelection(int position) { -        mSelection.remove(position); -        notifyDataSetChanged(); -    } - -    public void clearSelection() { -        mSelection.clear(); -        notifyDataSetChanged(); -    } - -    @Override -    public View getView(int position, View convertView, ViewGroup parent) { -        // let the adapter handle setting up the row views -        View v = super.getView(position, convertView, parent); - -        /** -         * Change color for multi-selection -         */ -        if (mSelection.get(position) != null && mSelection.get(position).booleanValue()) { -            // color for selected items -            v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis)); -        } else { -            // default color -            v.setBackgroundColor(Color.TRANSPARENT); -        } -        return v; -    } - -} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java deleted file mode 100644 index 0bffcaa19..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program.  If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui.adapter; - -import java.util.HashMap; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Color; -import android.support.v4.widget.CursorAdapter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -public class KeyListSecretAdapter extends CursorAdapter { -    private LayoutInflater mInflater; - -    private int mIndexUserId; - -    @SuppressLint("UseSparseArrays") -    private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>(); - -    public KeyListSecretAdapter(Context context, Cursor c, int flags) { -        super(context, c, flags); - -        mInflater = LayoutInflater.from(context); -        initIndex(c); -    } - -    @Override -    public Cursor swapCursor(Cursor newCursor) { -        initIndex(newCursor); - -        return super.swapCursor(newCursor); -    } - -    /** -     * Get column indexes for performance reasons just once in constructor and swapCursor. For a -     * performance comparison see http://stackoverflow.com/a/17999582 -     *  -     * @param cursor -     */ -    private void initIndex(Cursor cursor) { -        if (cursor != null) { -            mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID); -        } -    } - -    @Override -    public void bindView(View view, Context context, Cursor cursor) { -        TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); -        TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - -        String userId = cursor.getString(mIndexUserId); -        String[] userIdSplit = PgpKeyHelper.splitUserId(userId); - -        if (userIdSplit[0] != null) { -            mainUserId.setText(userIdSplit[0]); -        } else { -            mainUserId.setText(R.string.user_id_no_name); -        } -        if (userIdSplit[1] != null) { -            mainUserIdRest.setText(userIdSplit[1]); -        } else { -            mainUserIdRest.setText(""); -        } -    } - -    @Override -    public View newView(Context context, Cursor cursor, ViewGroup parent) { -        return mInflater.inflate(R.layout.key_list_secret_item, null); -    } - -    /** -------------------------- MULTI-SELECTION METHODS -------------- */ -    public void setNewSelection(int position, boolean value) { -        mSelection.put(position, value); -        notifyDataSetChanged(); -    } -     -    public void removeSelection(int position) { -        mSelection.remove(position); -        notifyDataSetChanged(); -    } - -    public void clearSelection() { -        mSelection.clear(); -        notifyDataSetChanged(); -    } - -    @Override -    public View getView(int position, View convertView, ViewGroup parent) { -        // let the adapter handle setting up the row views -        View v = super.getView(position, convertView, parent); - -        /** -         * Change color for multi-selection -         */ -        if (mSelection.get(position) != null) { -            // color for selected items -            v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis)); -        } else { -            // default color -            v.setBackgroundColor(Color.TRANSPARENT); -        } -        return v; -    } - -} diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_public_activity.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_activity.xml index f0e843e56..65d246d7b 100644 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_public_activity.xml +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_activity.xml @@ -9,8 +9,8 @@          android:layout_height="match_parent" >          <fragment -            android:id="@+id/key_list_public_fragment" -            android:name="org.sufficientlysecure.keychain.ui.KeyListPublicFragment" +            android:id="@+id/key_list_fragment" +            android:name="org.sufficientlysecure.keychain.ui.KeyListFragment"              android:layout_width="match_parent"              android:layout_height="match_parent" />      </FrameLayout> diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_public_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_fragment.xml index db82c8771..77bd6f4e9 100644 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_public_fragment.xml +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_fragment.xml @@ -7,7 +7,7 @@      <!--rebuild functionality of ListFragment -->      <LinearLayout -        android:id="@+id/key_list_public_progress_container" +        android:id="@+id/key_list_progress_container"          android:orientation="vertical"          android:layout_width="match_parent"          android:layout_height="match_parent" @@ -30,12 +30,12 @@      </LinearLayout>      <FrameLayout -        android:id="@+id/key_list_public_list_container" +        android:id="@+id/key_list_list_container"          android:layout_width="match_parent"          android:layout_height="match_parent">          <se.emilsjolander.stickylistheaders.StickyListHeadersListView -            android:id="@+id/key_list_public_list" +            android:id="@+id/key_list_list"              android:layout_width="match_parent"              android:layout_height="match_parent"              android:clipToPadding="false" @@ -47,7 +47,7 @@              android:scrollbarStyle="outsideOverlay" />          <LinearLayout -            android:id="@+id/key_list_public_empty" +            android:id="@+id/key_list_empty"              android:layout_width="match_parent"              android:layout_height="match_parent"              android:gravity="center" @@ -105,4 +105,4 @@      </FrameLayout> -</FrameLayout>
\ No newline at end of file +</FrameLayout> diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_header.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_header.xml new file mode 100644 index 000000000..09ac1c856 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_header.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +    android:layout_width="match_parent" +    android:layout_height="wrap_content" > + +    <org.sufficientlysecure.keychain.ui.widget.UnderlineTextView +        android:id="@+id/stickylist_header_text" +        android:layout_width="match_parent" +        android:layout_height="wrap_content" +        android:layout_gravity="start|left" +        android:padding="8dp" +        android:textColor="@color/emphasis" +        android:textSize="17sp" +        android:textStyle="bold" +        android:text="header text" /> + +    <TextView +        android:layout_width="wrap_content" +        android:layout_height="wrap_content" +        android:textAppearance="?android:attr/textAppearanceSmall" +        android:text="contact count" +        android:id="@+id/contacts_num" +        android:layout_centerVertical="true" +        android:layout_alignParentRight="true" +        android:layout_alignParentEnd="true" +        android:layout_marginRight="10px" +        android:visibility="visible" +        android:textColor="@android:color/darker_gray" /> + +</RelativeLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_public_item.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_item.xml index f07d60214..f52693138 100644 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_public_item.xml +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_item.xml @@ -3,13 +3,13 @@      android:layout_width="match_parent"      android:layout_height="wrap_content"      android:minHeight="?android:attr/listPreferredItemHeight" -    android:layout_marginRight="?android:attr/scrollbarSize"      android:gravity="center_vertical"      android:paddingLeft="8dp" -    android:paddingRight="8dp"      android:paddingTop="4dp"      android:paddingBottom="4dp" -    android:singleLine="true"> +    android:singleLine="true" +    android:descendantFocusability="blocksDescendants" +    android:focusable="false">      <TextView          android:id="@+id/mainUserId" @@ -17,6 +17,7 @@          android:layout_height="wrap_content"          android:text="@string/label_main_user_id"          android:textAppearance="?android:attr/textAppearanceMedium" +        android:layout_alignParentTop="true"          android:layout_alignParentLeft="true"          android:layout_alignParentStart="true" /> @@ -30,6 +31,23 @@          android:layout_alignParentLeft="true"          android:layout_alignParentStart="true" /> +    <Button +        style="@android:style/Widget.DeviceDefault.Button.Borderless.Small" +        android:layout_width="wrap_content" +        android:layout_height="wrap_content" +        android:id="@+id/edit" +        android:focusable="false" +        android:layout_alignTop="@+id/mainUserId" +        android:layout_alignParentRight="true" +        android:layout_alignParentEnd="true" +        android:layout_alignParentBottom="false" +        android:layout_alignParentTop="false" +        android:layout_alignBottom="@+id/mainUserIdRest" +        android:visibility="visible" +        android:enabled="true" +        android:textColor="@color/black" +        android:text="@string/edit" /> +      <TextView          android:id="@+id/revoked"          android:layout_width="wrap_content" @@ -37,9 +55,9 @@          android:textAppearance="?android:attr/textAppearanceSmall"          android:text="@string/revoked"          android:textColor="#e00" -        android:visibility="gone" +        android:visibility="visible"          android:layout_alignParentTop="true"          android:layout_alignParentRight="true"          android:layout_alignParentEnd="true" /> -</RelativeLayout>
\ No newline at end of file +</RelativeLayout> diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_public_header.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_public_header.xml deleted file mode 100644 index 5768e4153..000000000 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_public_header.xml +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" -    android:layout_width="match_parent" -    android:layout_height="wrap_content" > - -    <org.sufficientlysecure.keychain.ui.widget.UnderlineTextView -        android:id="@+id/stickylist_header_text" -        android:layout_width="match_parent" -        android:layout_height="wrap_content" -        android:layout_gravity="start|left" -        android:padding="8dp" -        android:textColor="@color/emphasis" -        android:textSize="17sp" -        android:textStyle="bold" /> - -</RelativeLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_secret_activity.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_secret_activity.xml deleted file mode 100644 index cd208a545..000000000 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_secret_activity.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" -    android:id="@+id/drawer_layout" -    android:layout_width="match_parent" -    android:layout_height="match_parent"> - -    <FrameLayout -        android:layout_width="match_parent" -        android:layout_height="match_parent"> - -        <fragment -            android:id="@+id/key_list_secret_fragment" -            android:name="org.sufficientlysecure.keychain.ui.KeyListSecretFragment" -            android:layout_width="match_parent" -            android:layout_height="match_parent" -            android:paddingBottom="16dp" -            android:paddingLeft="16dp" -            android:paddingRight="16dp" -            android:scrollbarStyle="outsideOverlay" /> -    </FrameLayout> - -    <include layout="@layout/drawer_list" /> - -</android.support.v4.widget.DrawerLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_secret_item.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_secret_item.xml deleted file mode 100644 index 7d5492265..000000000 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_secret_item.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" -    android:layout_width="match_parent" -    android:layout_height="wrap_content" -    android:minHeight="?android:attr/listPreferredItemHeight" -    android:gravity="center_vertical" -    android:paddingLeft="8dp" -    android:paddingTop="4dp" -    android:paddingBottom="4dp" -    android:singleLine="true"> - -    <TextView -        android:id="@+id/mainUserId" -        android:layout_width="wrap_content" -        android:layout_height="wrap_content" -        android:text="@string/label_main_user_id" -        android:textAppearance="?android:attr/textAppearanceMedium" -        android:layout_alignParentTop="true" -        android:layout_alignParentLeft="true" -        android:layout_alignParentStart="true" /> - -    <TextView -        android:id="@+id/mainUserIdRest" -        android:layout_width="wrap_content" -        android:layout_height="wrap_content" -        android:text="<user@example.com>" -        android:textAppearance="?android:attr/textAppearanceSmall" -        android:layout_below="@+id/mainUserId" -        android:layout_alignParentLeft="true" -        android:layout_alignParentStart="true" /> - -</RelativeLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml index b44ca82ec..3c8a4270b 100644 --- a/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml +++ b/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml @@ -160,7 +160,10 @@                      android:layout_height="wrap_content" />              </TableRow> -            <TableRow> +            <TableRow +                android:layout_width="fill_parent" +                android:layout_height="fill_parent" +                android:id="@+id/tableRow">                  <TextView                      android:layout_width="wrap_content" @@ -175,6 +178,22 @@                      android:layout_height="wrap_content"                      android:typeface="monospace" />              </TableRow> + +            <TableRow> + +                <TextView +                    android:layout_width="wrap_content" +                    android:layout_height="wrap_content" +                    android:layout_gravity="center_vertical" +                    android:paddingRight="10dip" +                    android:text="@string/label_secret_key" /> + +                <TextView +                    android:id="@+id/secret_key" +                    android:layout_width="match_parent" +                    android:layout_height="wrap_content" +                    android:typeface="monospace" /> +            </TableRow>          </TableLayout>          <TextView @@ -212,6 +231,17 @@              android:text="@string/section_actions" />          <com.beardedhen.androidbootstrap.BootstrapButton +            android:id="@+id/action_edit" +            android:layout_width="match_parent" +            android:layout_height="60dp" +            android:padding="4dp" +            android:layout_marginBottom="10dp" +            android:text="@string/key_view_action_edit" +            bootstrapbutton:bb_icon_left="fa-key" +            bootstrapbutton:bb_type="info" +            android:visibility="gone" /> + +        <com.beardedhen.androidbootstrap.BootstrapButton              android:id="@+id/action_encrypt"              android:layout_width="match_parent"              android:layout_height="60dp" diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_public.xml b/OpenPGP-Keychain/src/main/res/menu/key_list.xml index 35f4fca92..addef0c2d 100644 --- a/OpenPGP-Keychain/src/main/res/menu/key_list_public.xml +++ b/OpenPGP-Keychain/src/main/res/menu/key_list.xml @@ -3,18 +3,18 @@      xmlns:app="http://schemas.android.com/apk/res-auto">      <item -        android:id="@+id/menu_key_list_public_import" +        android:id="@+id/menu_key_list_import"          app:showAsAction="ifRoom|withText"          android:icon="@drawable/ic_action_add_person"          android:title="@string/menu_import" />      <item -        android:id="@+id/menu_key_list_public_export" +        android:id="@+id/menu_key_list_export"          app:showAsAction="never"          android:title="@string/menu_export_keys" />      <item -        android:id="@+id/menu_key_list_public_search" +        android:id="@+id/menu_key_list_search"          android:title="@string/menu_search"          android:icon="@drawable/ic_action_search"          app:actionViewClass="android.support.v7.widget.SearchView"          app:showAsAction="collapseActionView|ifRoom" /> -</menu>
\ No newline at end of file +</menu> diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_public_multi.xml b/OpenPGP-Keychain/src/main/res/menu/key_list_multi.xml index 9df17615e..db709052f 100644 --- a/OpenPGP-Keychain/src/main/res/menu/key_list_public_multi.xml +++ b/OpenPGP-Keychain/src/main/res/menu/key_list_multi.xml @@ -2,20 +2,20 @@  <menu xmlns:android="http://schemas.android.com/apk/res/android">      <item -        android:id="@+id/menu_key_list_public_multi_select_all" +        android:id="@+id/menu_key_list_multi_select_all"          android:icon="@drawable/ic_action_select_all"          android:title="@string/menu_select_all" />      <item -        android:id="@+id/menu_key_list_public_multi_export" +        android:id="@+id/menu_key_list_multi_export"          android:icon="@drawable/ic_action_import_export"          android:title="@string/menu_export_key" />      <item -        android:id="@+id/menu_key_list_public_multi_encrypt" +        android:id="@+id/menu_key_list_multi_encrypt"          android:icon="@drawable/ic_action_secure"          android:title="@string/menu_encrypt_to" />      <item -        android:id="@+id/menu_key_list_public_multi_delete" +        android:id="@+id/menu_key_list_multi_delete"          android:icon="@drawable/ic_action_discard"          android:title="@string/menu_delete_key" /> -</menu>
\ No newline at end of file +</menu> diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_secret.xml b/OpenPGP-Keychain/src/main/res/menu/key_list_secret.xml deleted file mode 100644 index 18a97ed72..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="always|withText" -        android:title="@string/menu_create_key" /> -    <item -        android:id="@+id/menu_key_list_secret_create_expert" -        app:showAsAction="never" -        android:title="@string/menu_create_key_expert" /> -    <item -        android:id="@+id/menu_key_list_secret_import" -        app:showAsAction="never" -        android:title="@string/menu_import" /> -    <item -        android:id="@+id/menu_key_list_secret_export" -        app:showAsAction="never" -        android:title="@string/menu_export_keys" /> - -</menu>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_secret_multi.xml b/OpenPGP-Keychain/src/main/res/menu/key_list_secret_multi.xml deleted file mode 100644 index e7e46815e..000000000 --- a/OpenPGP-Keychain/src/main/res/menu/key_list_secret_multi.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android"> - -    <item -        android:id="@+id/menu_key_list_public_multi_select_all" -        android:icon="@drawable/ic_action_select_all" -        android:title="@string/menu_select_all" /> -    <item -        android:id="@+id/menu_key_list_public_multi_export" -        android:icon="@drawable/ic_action_import_export" -        android:title="@string/menu_export_key" /> -    <item -        android:id="@+id/menu_key_list_public_multi_delete" -        android:icon="@drawable/ic_action_discard" -        android:title="@string/menu_delete_key" /> - -</menu>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/values/strings.xml b/OpenPGP-Keychain/src/main/res/values/strings.xml index 9babeb3f2..ca54db266 100644 --- a/OpenPGP-Keychain/src/main/res/values/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values/strings.xml @@ -155,6 +155,11 @@      <string name="revoked">revoked</string>      <string name="user_id">User ID</string> +    <plurals name="n_contacts"> +        <item quantity="one">1 contact</item> +        <item quantity="other">%d contacts</item> +    </plurals> +      <plurals name="n_key_servers">          <item quantity="one">%d keyserver</item>          <item quantity="other">%d keyservers</item> @@ -438,6 +443,7 @@      <string name="key_list_empty_button_import">importing keys.</string>      <!-- Key view --> +    <string name="key_view_action_edit">Edit this key</string>      <string name="key_view_action_encrypt">Encrypt to this contact</string>      <string name="key_view_action_certify">Certify this contact\'s key</string>      <string name="key_view_tab_main">Info</string> @@ -452,5 +458,10 @@      <string name="nav_apps">Registered Apps</string>      <string name="drawer_open">Open navigation drawer</string>      <string name="drawer_close">Close navigation drawer</string> +    <string name="edit">Edit</string> +    <string name="my_keys">My Keys</string> +    <string name="label_secret_key">Secret Key</string> +    <string name="secret_key_yes">available</string> +    <string name="secret_key_no">unavailable</string>  </resources>  | 
