diff options
| author | Vincent Breitmoser <valodim@mugenguild.com> | 2014-03-08 11:19:34 +0100 | 
|---|---|---|
| committer | Vincent Breitmoser <valodim@mugenguild.com> | 2014-03-08 11:19:34 +0100 | 
| commit | 8c6cb8b0ab9e89f9d575af829e0dd1dd9e82e401 (patch) | |
| tree | 2f68c772756535527b30cb629af87fabbf0afacc /OpenPGP-Keychain/src | |
| parent | 4851f7f8fc5ca82d60a55e95730bf6c11675979c (diff) | |
| download | open-keychain-8c6cb8b0ab9e89f9d575af829e0dd1dd9e82e401.tar.gz open-keychain-8c6cb8b0ab9e89f9d575af829e0dd1dd9e82e401.tar.bz2 open-keychain-8c6cb8b0ab9e89f9d575af829e0dd1dd9e82e401.zip | |
working unified list (no actions yet)
Diffstat (limited to 'OpenPGP-Keychain/src')
9 files changed, 450 insertions, 246 deletions
| 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 781f36758..85e01ae53 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/ui/DrawerActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java index 08ca262c3..d7644ce4d 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,7 +50,7 @@ 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 };      private Class mSelectedItem; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index dd7aa9a26..0bbe2edb1 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -37,7 +37,7 @@ public class KeyListActivity 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,19 @@ public class KeyListActivity 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:              mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR                      + "/pubexport.asc"); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 6f94ffd1a..a638796cd 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -17,25 +17,32 @@  package org.sufficientlysecure.keychain.ui; +import java.util.HashMap;  import java.util.ArrayList;  import java.util.Set; +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.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.KeyListAdapter;  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; @@ -46,6 +53,7 @@ import android.support.v4.app.Fragment;  import android.support.v4.app.LoaderManager;  import android.support.v4.content.CursorLoader;  import android.support.v4.content.Loader; +import android.support.v4.widget.CursorAdapter;  import android.view.ActionMode;  import android.view.LayoutInflater;  import android.view.Menu; @@ -55,7 +63,9 @@ import android.view.View.OnClickListener;  import android.view.ViewGroup;  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; @@ -207,7 +217,7 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick          // setListShown(false);          // Create an empty adapter we will use to display the loaded data. -        mAdapter = new KeyListAdapter(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, @@ -218,20 +228,21 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick      // 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();          // Now create and return a CursorLoader that will take care of          // creating a Cursor for the data being displayed. @@ -274,10 +285,14 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick          } else {              viewIntent = new Intent(getActivity(), ViewKeyActivityJB.class);          } -        viewIntent.setData(KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(id))); +        if(mAdapter.getKeyType(position) == KeyTypes.SECRET) { +            viewIntent.setData(KeychainContract.KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(mAdapter.getMasterKeyId(position)))); +        } else { +            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 @@ -335,4 +350,250 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick          deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");      } +    /** +     * Implements StickyListHeadersAdapter from library +     */ +    private class KeyListAdapter extends CursorAdapter implements StickyListHeadersAdapter { +        private LayoutInflater mInflater; +        private int mIndexUserId; +        private int mIndexIsRevoked; +        private int mMasterKeyId; + +        @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(userIdSplit[0]); +                } else { +                    mainUserId.setText(R.string.user_id_no_name); +                } +                if (userIdSplit[1] != null) { +                    mainUserIdRest.setText(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) { +                            Log.d(Constants.TAG, "swag"); +                            Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); +                            // editIntent.setData(KeychainContract.KeyRings.buildSecretKeyRingsUri(Long.toString(1))); +                            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); +                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) { +                holder.text.setText("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); +            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; +        } + +        /** +         * -------------------------- MULTI-SELECTION METHODS -------------- +         */ +        public void setNewSelection(int position, boolean value) { +            mSelection.put(position, value); +            notifyDataSetChanged(); +        } + +        public boolean isPositionChecked(int position) { +            Boolean result = mSelection.get(position); +            return result == null ? false : result; +        } + +        public Set<Integer> getCurrentCheckedPosition() { +            return mSelection.keySet(); +        } + +        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; +        } + +    } +  } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListAdapter.java deleted file mode 100644 index 2e0d1496b..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListAdapter.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.adapter; - -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; - -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 java.util.HashMap; -import java.util.Set; - -import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; - -/** - * Implements StickyListHeadersAdapter from library - */ -public class KeyListAdapter extends CursorAdapter 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 KeyListAdapter(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(userIdSplit[0]); -        } else { -            mainUserId.setText(R.string.user_id_no_name); -        } -        if (userIdSplit[1] != null) { -            mainUserIdRest.setText(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_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_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 boolean isPositionChecked(int position) { -        Boolean result = mSelection.get(position); -        return result == null ? false : result; -    } - -    public Set<Integer> getCurrentCheckedPosition() { -        return mSelection.keySet(); -    } - -    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; -    } - -} 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..5768e4153 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_header.xml @@ -0,0 +1,16 @@ +<?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_item.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_item.xml new file mode 100644 index 000000000..4ff0bbe65 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_item.xml @@ -0,0 +1,63 @@ +<?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" +    android:descendantFocusability="blocksDescendants" +    android:focusable="false"> + +    <TextView +        android:id="@+id/mainUserId" +        android:layout_width="wrap_content" +        android:layout_height="wrap_content" +        android:text="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" /> + +    <Button +        style="@android:style/Widget.DeviceDefault.Button.Borderless.Small" +        android:layout_width="wrap_content" +        android:layout_height="wrap_content" +        android:text="Edit" +        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" /> + +    <TextView +        android:id="@+id/revoked" +        android:layout_width="wrap_content" +        android:layout_height="wrap_content" +        android:textAppearance="?android:attr/textAppearanceSmall" +        android:text="@string/revoked" +        android:textColor="#e00" +        android:visibility="visible" +        android:layout_alignParentTop="true" +        android:layout_alignParentRight="true" +        android:layout_alignParentEnd="true" /> + +</RelativeLayout> 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 index 1ed86f730..77214074d 100644 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_secret_item.xml +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_secret_item.xml @@ -7,7 +7,8 @@      android:paddingLeft="8dp"      android:paddingTop="4dp"      android:paddingBottom="4dp" -    android:singleLine="true"> +    android:singleLine="true" +    android:descendantFocusability="blocksDescendants">      <TextView          android:id="@+id/mainUserId" @@ -29,4 +30,19 @@          android:layout_alignParentLeft="true"          android:layout_alignParentStart="true" /> -</RelativeLayout>
\ No newline at end of file +    <Button +        style="@android:style/Widget.DeviceDefault.Button.Borderless.Small" +        android:layout_width="wrap_content" +        android:layout_height="wrap_content" +        android:text="Edit" +        android:id="@+id/edit" +        android:enabled="false" +        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" /> + +</RelativeLayout> | 
