From d06eae018af74be12cda5090406942ad428548ba Mon Sep 17 00:00:00 2001 From: Tobias Erthal Date: Fri, 9 Sep 2016 19:06:56 +0200 Subject: [PATCH] Start implementing multi-select and action modes. Needs testing... --- .../keychain/ui/KeyListFragment.java | 441 +++++------------- .../ui/adapter/KeySectionedListAdapter.java | 415 ++++++++++++---- .../ui/util/adapter/CursorAdapter.java | 18 +- .../keychain/ui/util/adapter/KeyAdapter.java | 104 ----- .../ui/util/adapter/SectionCursorAdapter.java | 30 +- .../res/drawable-v21/list_item_ripple.xml | 12 + .../main/res/drawable/list_item_ripple.xml | 5 + .../src/main/res/layout/key_list_item.xml | 3 +- OpenKeychain/src/main/res/values/colors.xml | 3 + 9 files changed, 489 insertions(+), 542 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/KeyAdapter.java create mode 100644 OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml create mode 100644 OpenKeychain/src/main/res/drawable/list_item_ripple.xml diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 4ed45832d..43d2196ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -83,11 +83,13 @@ import java.util.ArrayList; * StickyListHeaders library which does not extend upon ListView. */ public class KeyListFragment extends RecyclerFragment - implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, + implements SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks, FabContainer, CryptoOperationHelper.Callback { static final int REQUEST_ACTION = 1; + static final String ORDER = KeyRings.HAS_ANY_SECRET + " DESC, " + KeyRings.USER_ID + " COLLATE NOCASE ASC"; + private static final int REQUEST_DELETE = 2; private static final int REQUEST_VIEW_KEY = 3; @@ -108,6 +110,101 @@ public class KeyListFragment extends RecyclerFragment // for ConsolidateOperation private CryptoOperationHelper mConsolidateOpHelper; + // Callbacks related to listview and menu events + private final ActionMode.Callback mActionCallback + = new ActionMode.Callback() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + getActivity().getMenuInflater().inflate(R.menu.key_list_multi, menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_key_list_multi_encrypt: { + long[] keyIds = getAdapter().getSelectedMasterKeyIds(); + Intent intent = new Intent(getActivity(), EncryptFilesActivity.class); + intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); + intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, keyIds); + + startActivityForResult(intent, REQUEST_ACTION); + mode.finish(); + break; + } + + case R.id.menu_key_list_multi_delete: { + long[] keyIds = getAdapter().getSelectedMasterKeyIds(); + boolean hasSecret = getAdapter().isAnySecretKeySelected(); + Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class); + intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, keyIds); + intent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, hasSecret); + if (hasSecret) { + intent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, + Preferences.getPreferences(getActivity()).getPreferredKeyserver()); + } + + startActivityForResult(intent, REQUEST_DELETE); + break; + } + } + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + mActionMode = null; + if(getAdapter() != null) { + getAdapter().finishSelection(); + } + } + }; + + private final KeySectionedListAdapter.KeyListListener mKeyListener + = new KeySectionedListAdapter.KeyListListener() { + @Override + public void onKeyDummyItemClicked() { + createKey(); + } + + @Override + public void onKeyItemClicked(long masterKeyId) { + Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class); + viewIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); + startActivityForResult(viewIntent, REQUEST_VIEW_KEY); + } + + @Override + public void onSlingerButtonClicked(long masterKeyId) { + Intent safeSlingerIntent = new Intent(getActivity(), SafeSlingerActivity.class); + safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, masterKeyId); + startActivityForResult(safeSlingerIntent, REQUEST_ACTION); + } + + @Override + public void onSelectionStateChanged(int selectedCount) { + if(selectedCount < 1) { + if(mActionMode != null) { + mActionMode.finish(); + } + } else { + if(mActionMode == null) { + mActionMode = getActivity().startActionMode(mActionCallback); + } + + String keysSelected = getResources().getQuantityString( + R.plurals.key_list_selected_keys, selectedCount, selectedCount); + mActionMode.setTitle(keysSelected); + } + } + }; + + /** * Load custom layout with StickyListView from library */ @@ -181,73 +278,6 @@ public class KeyListFragment extends RecyclerFragment //mStickyList.addFooterView(footer, null, false); */ - /* - * Multi-selection - */ - //mStickyList.setFastScrollAlwaysVisible(true); - - //mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); - - /* - mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() { - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - android.view.MenuInflater inflater = activity.getMenuInflater(); - inflater.inflate(R.menu.key_list_multi, menu); - mActionMode = mode; - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - - // get IDs for checked positions as long array - long[] ids; - - switch (item.getItemId()) { - case R.id.menu_key_list_multi_encrypt: { - ids = mAdapter.getCurrentSelectedMasterKeyIds(); - encrypt(mode, ids); - break; - } - case R.id.menu_key_list_multi_delete: { - ids = mAdapter.getCurrentSelectedMasterKeyIds(); - showDeleteKeyDialog(ids, mAdapter.isAnySecretSelected()); - break; - } - } - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - mActionMode = null; - mAdapter.clearSelection(); - } - - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { - if (checked) { - mAdapter.setNewSelection(position, true); - } else { - mAdapter.removeSelection(position); - } - int count = mStickyList.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); @@ -270,7 +300,10 @@ public class KeyListFragment extends RecyclerFragment // Create an empty adapter we will use to display the loaded data. //mAdapter = new KeyListAdapter(activity, null, 0); - setAdapter(new KeySectionedListAdapter(getContext(), null)); + KeySectionedListAdapter adapter = new KeySectionedListAdapter(getContext(), null); + adapter.setKeyListener(mKeyListener); + + setAdapter(adapter); setLayoutManager(new LayoutManager(getActivity())); // Prepare the loader. Either re-connect with an existing one, @@ -290,9 +323,6 @@ public class KeyListFragment extends RecyclerFragment startActivity(searchIntent); } - static final String ORDER = - KeyRings.HAS_ANY_SECRET + " DESC, " + KeyRings.USER_ID + " COLLATE NOCASE ASC"; - @Override public Loader onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This @@ -314,24 +344,7 @@ public class KeyListFragment extends RecyclerFragment // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) getAdapter().setSearchQuery(mQuery); - - if (data != null && (mQuery == null || TextUtils.isEmpty(mQuery))) { - boolean isSecret = data.moveToFirst() && data.getInt(KeyAdapter.INDEX_HAS_ANY_SECRET) != 0; - if (!isSecret) { - MatrixCursor headerCursor = new MatrixCursor(KeyAdapter.PROJECTION); - Long[] row = new Long[KeyAdapter.PROJECTION.length]; - row[KeyAdapter.INDEX_HAS_ANY_SECRET] = 1L; - row[KeyAdapter.INDEX_MASTER_KEY_ID] = 0L; - headerCursor.addRow(row); - - Cursor dataCursor = data; - data = new MergeCursor(new Cursor[] { - headerCursor, dataCursor - }); - } - } - - getAdapter().swapCursor(data); + getAdapter().swapCursor(KeySectionedListAdapter.KeyCursor.wrap(data)); // end action mode, if any if (mActionMode != null) { @@ -354,44 +367,6 @@ public class KeyListFragment extends RecyclerFragment getAdapter().swapCursor(null); } - /** - * On click on item, start key view activity - */ - @Override - public void onItemClick(AdapterView adapterView, View view, int position, long id) { - Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class); - viewIntent.setData( - KeyRings.buildGenericKeyRingUri(getAdapter().getMasterKeyId(position))); - startActivityForResult(viewIntent, REQUEST_VIEW_KEY); - } - - protected void encrypt(ActionMode mode, long[] masterKeyIds) { - Intent intent = new Intent(getActivity(), EncryptFilesActivity.class); - intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); - intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, masterKeyIds); - // used instead of startActivity set actionbar based on callingPackage - startActivityForResult(intent, REQUEST_ACTION); - - mode.finish(); - } - - /** - * Show dialog to delete key - * - * @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not - */ - public void showDeleteKeyDialog(long[] masterKeyIds, boolean hasSecret) { - Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class); - intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, masterKeyIds); - intent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, hasSecret); - if (hasSecret) { - intent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, - Preferences.getPreferences(getActivity()).getPreferredKeyserver()); - } - startActivityForResult(intent, REQUEST_DELETE); - } - - @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.key_list, menu); @@ -406,7 +381,6 @@ public class KeyListFragment extends RecyclerFragment // Get the searchview MenuItem searchItem = menu.findItem(R.id.menu_key_list_search); - SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); // Execute this when searching @@ -551,7 +525,6 @@ public class KeyListFragment extends RecyclerFragment } ProviderHelper providerHelper = new ProviderHelper(activity); - Cursor cursor = providerHelper.getContentResolver().query( KeyRings.buildUnifiedKeyRingsUri(), new String[]{ KeyRings.FINGERPRINT @@ -566,7 +539,7 @@ public class KeyListFragment extends RecyclerFragment ArrayList keyList = new ArrayList<>(); try { while (cursor.moveToNext()) { - byte[] blob = cursor.getBlob(0);//fingerprint column is 0 + byte[] blob = cursor.getBlob(0); //fingerprint column is 0 String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); keyList.add(keyEntry); @@ -585,7 +558,6 @@ public class KeyListFragment extends RecyclerFragment } private void consolidate() { - CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @@ -615,14 +587,11 @@ public class KeyListFragment extends RecyclerFragment } }; - mConsolidateOpHelper = - new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); - + mConsolidateOpHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); mConsolidateOpHelper.cryptoOperation(); } private void benchmark() { - CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @@ -652,9 +621,7 @@ public class KeyListFragment extends RecyclerFragment } }; - CryptoOperationHelper opHelper = - new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); - + CryptoOperationHelper opHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); opHelper.cryptoOperation(); } @@ -673,6 +640,7 @@ public class KeyListFragment extends RecyclerFragment if (mActionMode != null) { mActionMode.finish(); } + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); result.createNotify(getActivity()).show(); @@ -744,199 +712,4 @@ public class KeyListFragment extends RecyclerFragment public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } - - /* - public class KeyListAdapter extends KeyAdapter implements StickyListHeadersAdapter { - - private HashMap mSelection = new HashMap<>(); - - private Context mContext; - - public KeyListAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - mContext = context; - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - View view = super.newView(context, cursor, parent); - - final KeyItemViewHolder holder = (KeyItemViewHolder) view.getTag(); - - holder.mSlinger.setVisibility(View.VISIBLE); - - ContentDescriptionHint.setup(holder.mSlingerButton); - holder.mSlingerButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (holder.mMasterKeyId != null) { - Intent safeSlingerIntent = new Intent(mContext, SafeSlingerActivity.class); - safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, holder.mMasterKeyId); - startActivityForResult(safeSlingerIntent, REQUEST_ACTION); - } - } - }); - - return view; - } - - @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); - - int colorEmphasis = FormattingUtils.getColorFromAttr(mContext, R.attr.colorEmphasis); - - if (mSelection.get(position) != null) { - // selected position color - v.setBackgroundColor(colorEmphasis); - } else { - // default color - v.setBackgroundColor(Color.TRANSPARENT); - } - - return v; - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0; - long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); - if (isSecret && masterKeyId == 0L) { - - // sort of a hack: if this item isn't enabled, we make it clickable - // to intercept its click events - view.setClickable(true); - - KeyItemViewHolder h = (KeyItemViewHolder) view.getTag(); - h.setDummy(new OnClickListener() { - @Override - public void onClick(View v) { - createKey(); - } - }); - return; - } - - super.bindView(view, context, cursor); - } - - private class HeaderViewHolder { - TextView mText; - TextView mCount; - } - - - @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_public, parent, false); - holder.mText = (TextView) convertView.findViewById(R.id.stickylist_header_text); - holder.mCount = (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(INDEX_HAS_ANY_SECRET) != 0) { - { // set contact count - int num = mCursor.getCount(); - // If this is a dummy secret key, subtract one - if (mCursor.getLong(INDEX_MASTER_KEY_ID) == 0L) { - num -= 1; - } - String contactsTotal = mContext.getResources().getQuantityString(R.plurals.n_keys, num, num); - holder.mCount.setText(contactsTotal); - holder.mCount.setVisibility(View.VISIBLE); - } - - holder.mText.setText(convertView.getResources().getString(R.string.my_keys)); - return convertView; - } - - // set header text as first char in user id - String userId = mCursor.getString(INDEX_USER_ID); - String headerText = convertView.getResources().getString(R.string.user_id_no_name); - if (userId != null && userId.length() > 0) { - headerText = "" + userId.charAt(0); - } - holder.mText.setText(headerText); - holder.mCount.setVisibility(View.GONE); - return convertView; - } - - @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(INDEX_HAS_ANY_SECRET) != 0) { - return 1L; - } - // otherwise, return the first character of the name as ID - String userId = mCursor.getString(INDEX_USER_ID); - if (userId != null && userId.length() > 0) { - return Character.toUpperCase(userId.charAt(0)); - } else { - return Long.MAX_VALUE; - } - } - - public void setNewSelection(int position, boolean value) { - mSelection.put(position, value); - notifyDataSetChanged(); - } - - public boolean isAnySecretSelected() { - for (int pos : mSelection.keySet()) { - if (isSecretAvailable(pos)) { - return true; - } - } - return false; - } - - public long[] getCurrentSelectedMasterKeyIds() { - long[] ids = new long[mSelection.size()]; - int i = 0; - // get master key ids - for (int pos : mSelection.keySet()) { - ids[i++] = getMasterKeyId(pos); - } - return ids; - } - - public void removeSelection(int position) { - mSelection.remove(position); - notifyDataSetChanged(); - } - - public void clearSelection() { - mSelection.clear(); - notifyDataSetChanged(); - } - - } -*/ - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java index 420e88214..521d24632 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java @@ -2,11 +2,14 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; import android.database.Cursor; +import android.database.CursorWrapper; +import android.database.MatrixCursor; +import android.database.MergeCursor; +import android.graphics.Color; import android.graphics.PorterDuff; import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.text.format.DateUtils; -import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -17,13 +20,21 @@ import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.adapter.*; import org.sufficientlysecure.keychain.util.Log; -public class KeySectionedListAdapter extends SectionCursorAdapter implements org.sufficientlysecure.keychain.ui.util.adapter.KeyAdapter { +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class KeySectionedListAdapter extends SectionCursorAdapter { + private static final short VIEW_ITEM_TYPE_KEY = 0x0; private static final short VIEW_ITEM_TYPE_DUMMY = 0x1; @@ -31,57 +42,109 @@ public class KeySectionedListAdapter extends SectionCursorAdapter mSelected; + private KeyListListener mListener; + private boolean mHasDummy = false; public KeySectionedListAdapter(Context context, Cursor cursor) { - super(context, cursor, 0); + super(context, KeyCursor.wrap(cursor), 0); mQuery = ""; - mSelectionMap = new SparseBooleanArray(); + mSelected = new ArrayList<>(); } - @Override public void setSearchQuery(String query) { mQuery = query; } - @Override - public boolean isEnabled(Cursor cursor) { - return true; - } - - @Override - public KeyItem getItem(int position) { - Cursor cursor = getCursor(); - - if(cursor != null) { - if(cursor.getPosition() != position) { - moveCursor(position); - } - - return new KeyItem(cursor); - } - - return null; - } - - @Override - public long getMasterKeyId(int position) { - return 0; - } - - @Override - public boolean isSecretAvailable(int position) { - return false; - } @Override public void onContentChanged() { mHasDummy = false; + mSelected.clear(); + + if(mListener != null) { + mListener.onSelectionStateChanged(0); + } + super.onContentChanged(); } + @Override + public KeyCursor swapCursor(KeyCursor cursor) { + if (cursor != null && (mQuery == null || TextUtils.isEmpty(mQuery))) { + boolean isSecret = cursor.moveToFirst() && cursor.isSecret(); + + if (!isSecret) { + MatrixCursor headerCursor = new MatrixCursor(KeyCursor.PROJECTION); + Long[] row = new Long[KeyCursor.PROJECTION.length]; + row[KeyCursor.INDEX_HAS_ANY_SECRET] = 1L; + row[KeyCursor.INDEX_MASTER_KEY_ID] = 0L; + headerCursor.addRow(row); + + Cursor[] toMerge = { + headerCursor, + cursor.getWrappedCursor() + }; + + cursor = KeyCursor.wrap(new MergeCursor(toMerge)); + } + } + + return (KeyCursor) super.swapCursor(cursor); + } + + public void setKeyListener(KeyListListener listener) { + mListener = listener; + } + + private int getSelectedCount() { + return mSelected.size(); + } + + private void selectPosition(int position) { + mSelected.add(position); + notifyItemChanged(position); + } + + private void deselectPosition(int position) { + mSelected.remove(Integer.valueOf(position)); + notifyItemChanged(position); + } + + private boolean isSelected(int position) { + return mSelected.contains(position); + } + + public long[] getSelectedMasterKeyIds() { + long[] keys = new long[mSelected.size()]; + for(int i = 0; i < keys.length; i++) { + if(!moveCursor(mSelected.get(i))) { + return keys; + } + + keys[i] = getIdFromCursor(getCursor()); + } + + return keys; + } + + public boolean isAnySecretKeySelected() { + for(int i = 0; i < mSelected.size(); i++) { + if(!moveCursor(mSelected.get(i))) { + return false; + } + + if(getCursor().isSecret()) { + return true; + } + } + + return false; + } + + /** * Returns the number of database entries displayed. * @return The item count @@ -95,15 +158,20 @@ public class KeySectionedListAdapter extends SectionCursorAdapter 0L; + } + + public boolean hasEncrypt() { + return getInt(INDEX_HAS_ENCRYPT) != 0; + } + + public long getCreationTime() { + return getLong(INDEX_CREATION) * 1000; + } + + public Date getCreationDate() { + return new Date(getCreationTime()); + } + + public byte[] getRawFingerprint() { + return getBlob(INDEX_FINGERPRINT); + } + + public String getFingerprint() { + return KeyFormattingUtils.convertFingerprintToHex(getRawFingerprint()); + } + + public boolean isSecret() { + return getInt(INDEX_HAS_ANY_SECRET) != 0; + } + + public boolean isRevoked() { + return getInt(INDEX_IS_REVOKED) > 0; + } + + public boolean isExpired() { + return getInt(INDEX_IS_EXPIRED) > 0; + } + + public boolean isVerified() { + return getInt(INDEX_VERIFIED) > 0; + } + } + + public interface KeyListListener { + void onKeyDummyItemClicked(); + void onKeyItemClicked(long masterKeyId); + void onSlingerButtonClicked(long masterKeyId); + void onSelectionStateChanged(int selectedCount); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java index 57315ea5e..3b54c4488 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java @@ -9,11 +9,11 @@ import android.support.v7.widget.RecyclerView; import org.sufficientlysecure.keychain.util.Log; -public abstract class CursorAdapter extends RecyclerView.Adapter { +public abstract class CursorAdapter extends RecyclerView.Adapter { public static final String TAG = "CursorAdapter"; - private Cursor mCursor; + private C mCursor; private Context mContext; private boolean mDataValid; @@ -39,7 +39,7 @@ public abstract class CursorAdapter extends RecyclerView.Adapter { * @param c The cursor from which to get the data. * @param context The context */ - public CursorAdapter(Context context, Cursor c) { + public CursorAdapter(Context context, C c) { init(context, c, FLAG_REGISTER_CONTENT_OBSERVER); } @@ -51,11 +51,11 @@ public abstract class CursorAdapter extends RecyclerView.Adapter { * @param flags Flags used to determine the behavior of the adapter * @see #FLAG_REGISTER_CONTENT_OBSERVER */ - public CursorAdapter(Context context, Cursor c, int flags) { + public CursorAdapter(Context context, C c, int flags) { init(context, c, flags); } - private void init(Context context, Cursor c, int flags) { + private void init(Context context, C c, int flags) { boolean cursorPresent = c != null; mCursor = c; mDataValid = cursorPresent; @@ -80,7 +80,7 @@ public abstract class CursorAdapter extends RecyclerView.Adapter { * Returns the cursor. * @return the cursor. */ - public Cursor getCursor() { + public C getCursor() { return mCursor; } @@ -140,7 +140,7 @@ public abstract class CursorAdapter extends RecyclerView.Adapter { * @param cursor The cursor moved to the correct position. * @return The id of the dataset */ - public long getIdFromCursor(Cursor cursor) { + public long getIdFromCursor(C cursor) { if(cursor != null) { return cursor.getPosition(); } else { @@ -185,7 +185,7 @@ public abstract class CursorAdapter extends RecyclerView.Adapter { * * @param cursor The new cursor to be used */ - public void changeCursor(Cursor cursor) { + public void changeCursor(C cursor) { Cursor old = swapCursor(cursor); if (old != null) { old.close(); @@ -202,7 +202,7 @@ public abstract class CursorAdapter extends RecyclerView.Adapter { * If the given new Cursor is the same instance is the previously set * Cursor, null is also returned. */ - public Cursor swapCursor(Cursor newCursor) { + public Cursor swapCursor(C newCursor) { if (newCursor == mCursor) { return null; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/KeyAdapter.java deleted file mode 100644 index f64723f85..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/KeyAdapter.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.sufficientlysecure.keychain.ui.util.adapter; - -import android.database.Cursor; - -import org.openintents.openpgp.util.OpenPgpUtils; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; - -import java.io.Serializable; -import java.util.Date; - -public interface KeyAdapter { - // These are the rows that we will retrieve. - String[] PROJECTION = new String[] { - KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.IS_EXPIRED, - KeychainContract.KeyRings.VERIFIED, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID, - KeychainContract.KeyRings.FINGERPRINT, - KeychainContract.KeyRings.CREATION, - KeychainContract.KeyRings.HAS_ENCRYPT - }; - - // projection indices - int INDEX_MASTER_KEY_ID = 1; - int INDEX_USER_ID = 2; - int INDEX_IS_REVOKED = 3; - int INDEX_IS_EXPIRED = 4; - int INDEX_VERIFIED = 5; - int INDEX_HAS_ANY_SECRET = 6; - int INDEX_HAS_DUPLICATE_USER_ID = 7; - int INDEX_FINGERPRINT = 8; - int INDEX_CREATION = 9; - int INDEX_HAS_ENCRYPT = 10; - - // adapter functionality - void setSearchQuery(String query); - boolean isEnabled(Cursor cursor); - - KeyItem getItem(int position); - long getMasterKeyId(int position); - boolean isSecretAvailable(int position); - - class KeyItem implements Serializable { - public final String mUserIdFull; - public final OpenPgpUtils.UserId mUserId; - public final long mKeyId; - public final boolean mHasDuplicate; - public final boolean mHasEncrypt; - public final Date mCreation; - public final String mFingerprint; - public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified; - - public KeyItem(Cursor cursor) { - String userId = cursor.getString(INDEX_USER_ID); - mUserId = KeyRing.splitUserId(userId); - mUserIdFull = userId; - mKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); - mHasDuplicate = cursor.getLong(INDEX_HAS_DUPLICATE_USER_ID) > 0; - mHasEncrypt = cursor.getInt(INDEX_HAS_ENCRYPT) != 0; - mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000); - mFingerprint = KeyFormattingUtils.convertFingerprintToHex( - cursor.getBlob(INDEX_FINGERPRINT)); - mIsSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0; - mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; - mIsExpired = cursor.getInt(INDEX_IS_EXPIRED) > 0; - mIsVerified = cursor.getInt(INDEX_VERIFIED) > 0; - } - - public KeyItem(CanonicalizedPublicKeyRing ring) { - CanonicalizedPublicKey key = ring.getPublicKey(); - String userId = key.getPrimaryUserIdWithFallback(); - mUserId = KeyRing.splitUserId(userId); - mUserIdFull = userId; - mKeyId = ring.getMasterKeyId(); - mHasDuplicate = false; - mHasEncrypt = key.getKeyRing().getEncryptIds().size() > 0; - mCreation = key.getCreationTime(); - mFingerprint = KeyFormattingUtils.convertFingerprintToHex( - ring.getFingerprint()); - mIsRevoked = key.isRevoked(); - mIsExpired = key.isExpired(); - - // these two are actually "don't know"s - mIsSecret = false; - mIsVerified = false; - } - - public String getReadableName() { - if (mUserId.name != null) { - return mUserId.name; - } else { - return mUserId.email; - } - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java index e31e0943e..d92a9ac0f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java @@ -9,13 +9,14 @@ import android.view.ViewGroup; import com.tonicartos.superslim.LayoutManager; import org.sufficientlysecure.keychain.util.Log; + /** * @param section type. * @param the view holder extending {@code BaseViewHolder} that is bound to the cursor data. * @param the view holder extending {@code BaseViewHolder<>} that is bound to the section data. */ -public abstract class SectionCursorAdapter extends CursorAdapter { +public abstract class SectionCursorAdapter extends CursorAdapter { public static final String TAG = "SectionCursorAdapter"; @@ -25,7 +26,7 @@ public abstract class SectionCursorAdapter mSectionMap = new SparseArrayCompat<>(); private Comparator mSectionComparator; - public SectionCursorAdapter(Context context, Cursor cursor, int flags) { + public SectionCursorAdapter(Context context, C cursor, int flags) { this(context, cursor, flags, new Comparator() { @Override public boolean equal(T obj1, T obj2) { @@ -35,7 +36,7 @@ public abstract class SectionCursorAdapter comparator) { + public SectionCursorAdapter(Context context, C cursor, int flags, Comparator comparator) { super(context, cursor, flags); setSectionComparator(comparator); } @@ -82,7 +83,7 @@ public abstract class SectionCursorAdapter cursorPosition) { + return cursorPosition; + } + + cursorPosition +=1; + } + + return cursorPosition; + } + /** * Given the list position of an item in the adapter, returns the * adapter position of the first item of the section the given item belongs to. @@ -263,7 +277,7 @@ public abstract class SectionCursorAdapter { boolean equal(T obj1, T obj2); diff --git a/OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml b/OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml new file mode 100644 index 000000000..32d726ac1 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/drawable/list_item_ripple.xml b/OpenKeychain/src/main/res/drawable/list_item_ripple.xml new file mode 100644 index 000000000..ae8972031 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable/list_item_ripple.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/key_list_item.xml b/OpenKeychain/src/main/res/layout/key_list_item.xml index da773b328..2ee819328 100644 --- a/OpenKeychain/src/main/res/layout/key_list_item.xml +++ b/OpenKeychain/src/main/res/layout/key_list_item.xml @@ -5,11 +5,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeight" + android:minHeight="?attr/listPreferredItemHeight" android:gravity="center_vertical" android:maxLines="1" android:orientation="horizontal" android:descendantFocusability="blocksDescendants" + android:background="@drawable/list_item_ripple" android:focusable="false"> #7bad45 #1E7bad45 + #0c000000 + #2c000000 +