diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteSelectPubKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteSelectPubKeyActivity.java index f716103c8..ebf53cbaa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteSelectPubKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteSelectPubKeyActivity.java @@ -25,14 +25,12 @@ import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.style.BulletSpan; -import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.view.View; import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import java.util.ArrayList; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java new file mode 100644 index 000000000..e9b93d1ac --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * Copyright (C) 2010-2014 Thialfihar + * + * 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 . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.ContextCompat; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import org.openintents.openpgp.util.OpenPgpUtils; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.remote.ui.adapter.SelectEncryptKeyAdapter; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerFragment; + +public class SelectPublicKeyFragment extends RecyclerFragment + implements TextWatcher, LoaderManager.LoaderCallbacks { + public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids"; + + private EditText mSearchView; + private long mSelectedMasterKeyIds[]; + private String mQuery; + + /** + * Creates new instance of this fragment + */ + public static SelectPublicKeyFragment newInstance(long[] preselectedKeyIds) { + SelectPublicKeyFragment frag = new SelectPublicKeyFragment(); + Bundle args = new Bundle(); + + args.putLongArray(ARG_PRESELECTED_KEY_IDS, preselectedKeyIds); + frag.setArguments(args); + + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mSelectedMasterKeyIds = getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final Context context = getContext(); + FrameLayout root = new FrameLayout(context); + + LinearLayout progressContainer = new LinearLayout(context); + progressContainer.setId(INTERNAL_PROGRESS_CONTAINER_ID); + progressContainer.setOrientation(LinearLayout.VERTICAL); + progressContainer.setGravity(Gravity.CENTER); + progressContainer.setVisibility(View.GONE); + + ProgressBar progressBar = new ProgressBar(context, null, + android.R.attr.progressBarStyleLarge); + + progressContainer.addView(progressBar, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + root.addView(progressContainer, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + FrameLayout listContainer = new FrameLayout(context); + listContainer.setId(INTERNAL_LIST_CONTAINER_ID); + + TextView textView = new TextView(context); + textView.setId(INTERNAL_EMPTY_VIEW_ID); + textView.setGravity(Gravity.CENTER); + + listContainer.addView(textView, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + LinearLayout innerListContainer = new LinearLayout(context); + innerListContainer.setOrientation(LinearLayout.VERTICAL); + + mSearchView = new EditText(context); + mSearchView.setId(android.R.id.input); + mSearchView.setHint(R.string.menu_search); + mSearchView.setCompoundDrawablesWithIntrinsicBounds( + ContextCompat.getDrawable( + context, + R.drawable.ic_search_grey_24dp + ), null, null, null); + + innerListContainer.addView(mSearchView, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + RecyclerView listView = new RecyclerView(context); + listView.setId(INTERNAL_LIST_VIEW_ID); + + int padding = FormattingUtils.dpToPx(context, 8); + listView.setPadding(padding, 0, padding, 0); + listView.setClipToPadding(false); + + innerListContainer.addView(listView, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + listContainer.addView(innerListContainer, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + root.addView(listContainer, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + + root.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + return root; + } + + /** + * Define Adapter and Loader on create of Activity + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + // 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)); + mSearchView.addTextChangedListener(this); + + setAdapter(new SelectEncryptKeyAdapter(getContext(), null)); + setLayoutManager(new LinearLayoutManager(getContext())); + + // Start out with a progress indicator. + hideList(false); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getLoaderManager().initLoader(0, null, this); + } + + public long[] getSelectedMasterKeyIds() { + return getAdapter() != null ? + getAdapter().getMasterKeyIds() : new long[0]; + } + + public String[] getSelectedRawUserIds() { + return getAdapter() != null ? + getAdapter().getRawUserIds() : new String[0]; + } + + public OpenPgpUtils.UserId[] getSelectedUserIds() { + return getAdapter() != null ? + getAdapter().getUserIds() : new OpenPgpUtils.UserId[0]; + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); + + // These are the rows that we will retrieve. + String[] projection = new String[]{ + KeyRings._ID, + KeyRings.MASTER_KEY_ID, + KeyRings.USER_ID, + KeyRings.IS_EXPIRED, + KeyRings.IS_REVOKED, + KeyRings.HAS_ENCRYPT, + KeyRings.VERIFIED, + KeyRings.HAS_DUPLICATE_USER_ID, + KeyRings.CREATION, + }; + + String inMasterKeyList = null; + if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) { + inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN ("; + for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) { + if (i != 0) { + inMasterKeyList += ", "; + } + inMasterKeyList += DatabaseUtils.sqlEscapeString("" + mSelectedMasterKeyIds[i]); + } + inMasterKeyList += ")"; + } + + String orderBy = KeyRings.USER_ID + " ASC"; + if (inMasterKeyList != null) { + // sort by selected master keys + orderBy = inMasterKeyList + " DESC, " + orderBy; + } + String where = null; + String whereArgs[] = null; + if (mQuery != null) { + String[] words = mQuery.trim().split("\\s+"); + whereArgs = new String[words.length]; + for (int i = 0; i < words.length; ++i) { + if (where == null) { + where = ""; + } else { + where += " AND "; + } + where += KeyRings.USER_ID + " LIKE ?"; + whereArgs[i] = "%" + words[i] + "%"; + } + } + + // 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, where, whereArgs, orderBy); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + getAdapter().setQuery(mQuery); + getAdapter().swapCursor(SelectEncryptKeyAdapter.PublicKeyCursor.wrap(data)); + + // The list should now be shown. + if (isResumed()) { + showList(true); + } else { + showList(false); + } + + // preselect given master keys + getAdapter().preselectMasterKeyIds(mSelectedMasterKeyIds); + } + + @Override + public void onLoaderReset(Loader 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. + getAdapter().swapCursor(null); + } + + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void afterTextChanged(Editable editable) { + mQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null; + getLoaderManager().restartLoader(0, null, this); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java index 946c77ce8..7c77f4599 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java @@ -39,7 +39,7 @@ public class SelectSignKeyIdActivity extends BaseActivity { public static final String EXTRA_USER_ID = OpenPgpApi.EXTRA_USER_ID; public static final String EXTRA_DATA = "data"; - private static final int REQUEST_CODE_CREATE_KEY = 0x00008884; + protected static final int REQUEST_CODE_CREATE_KEY = 0x00008884; private String mPreferredUserId; private Intent mData; @@ -58,13 +58,6 @@ public class SelectSignKeyIdActivity extends BaseActivity { } }); - TextView createKeyButton = (TextView) findViewById(R.id.api_select_sign_key_create_key); - createKeyButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - createKey(mPreferredUserId); - } - }); TextView noneButton = (TextView) findViewById(R.id.api_select_sign_key_none); noneButton.setOnClickListener(new View.OnClickListener() { @Override @@ -86,20 +79,11 @@ public class SelectSignKeyIdActivity extends BaseActivity { return; } else { Log.d(Constants.TAG, "uri: " + appUri); - startListFragments(savedInstanceState, appUri, mData); + startListFragments(savedInstanceState, appUri, mData, mPreferredUserId); } } - private void createKey(String userId) { - OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(userId); - - Intent intent = new Intent(this, CreateKeyActivity.class); - intent.putExtra(CreateKeyActivity.EXTRA_NAME, userIdSplit.name); - intent.putExtra(CreateKeyActivity.EXTRA_EMAIL, userIdSplit.email); - startActivityForResult(intent, REQUEST_CODE_CREATE_KEY); - } - - private void startListFragments(Bundle savedInstanceState, Uri dataUri, Intent data) { + private void startListFragments(Bundle savedInstanceState, Uri dataUri, Intent data, String preferredUserId) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -108,7 +92,8 @@ public class SelectSignKeyIdActivity extends BaseActivity { } // Create an instance of the fragments - SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment.newInstance(dataUri, data); + SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment + .newInstance(dataUri, data, preferredUserId); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index 73ce5fe47..5eaaac3c8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.remote.ui; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -27,42 +26,44 @@ import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListView; +import android.support.v7.widget.LinearLayoutManager; import org.openintents.openpgp.util.OpenPgpApi; +import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; +import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; -import org.sufficientlysecure.keychain.ui.widget.FixedListView; +import org.sufficientlysecure.keychain.remote.ui.adapter.SelectSignKeyAdapter; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity; +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerFragment; import org.sufficientlysecure.keychain.util.Log; -public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks { +public class SelectSignKeyIdListFragment extends RecyclerFragment + implements SelectSignKeyAdapter.SelectSignKeyListener, LoaderManager.LoaderCallbacks { private static final String ARG_DATA_URI = "uri"; + private static final String ARG_PREF_UID = "pref_uid"; public static final String ARG_DATA = "data"; - private SelectKeyCursorAdapter mAdapter; - private ApiDataAccessObject mApiDao; - private Uri mDataUri; + private Intent mResult; + private String mPrefUid; + private ApiDataAccessObject mApiDao; /** * Creates new instance of this fragment */ - public static SelectSignKeyIdListFragment newInstance(Uri dataUri, Intent data) { + public static SelectSignKeyIdListFragment newInstance(Uri dataUri, Intent data, String preferredUserId) { SelectSignKeyIdListFragment frag = new SelectSignKeyIdListFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DATA_URI, dataUri); args.putParcelable(ARG_DATA, data); + args.putString(ARG_PREF_UID, preferredUserId); frag.setArguments(args); @@ -72,33 +73,9 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mApiDao = new ApiDataAccessObject(getActivity()); } - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View layout = super.onCreateView(inflater, container, - savedInstanceState); - ListView lv = (ListView) layout.findViewById(android.R.id.list); - ViewGroup parent = (ViewGroup) lv.getParent(); - - /* - * http://stackoverflow.com/a/15880684 - * Remove ListView and add FixedListView in its place. - * This is done here programatically to be still able to use the progressBar of ListFragment. - * - * We want FixedListView to be able to put this ListFragment inside a ScrollView - */ - int lvIndex = parent.indexOfChild(lv); - parent.removeViewAt(lvIndex); - FixedListView newLv = new FixedListView(getActivity()); - newLv.setId(android.R.id.list); - parent.addView(newLv, lvIndex, lv.getLayoutParams()); - return layout; - } - /** * Define Adapter and Loader on create of Activity */ @@ -106,36 +83,19 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + mResult = getArguments().getParcelable(ARG_DATA); + mPrefUid = getArguments().getString(ARG_PREF_UID); mDataUri = getArguments().getParcelable(ARG_DATA_URI); - final Intent resultData = getArguments().getParcelable(ARG_DATA); - - getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); - getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - long masterKeyId = mAdapter.getMasterKeyId(position); - - Uri allowedKeysUri = mDataUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build(); - Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri); - mApiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId); - - resultData.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId); - - getActivity().setResult(Activity.RESULT_OK, resultData); - getActivity().finish(); - } - }); // 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)); - mAdapter = new SecretKeyCursorAdapter(getActivity(), null, 0, getListView()); - - setListAdapter(mAdapter); + setAdapter(new SelectSignKeyAdapter(getContext(), null)); + setLayoutManager(new LinearLayoutManager(getContext())); // Start out with a progress indicator. - setListShown(false); + hideList(false); // Prepare the loader. Either re-connect with an existing one, // or start a new one. @@ -172,13 +132,13 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) - mAdapter.swapCursor(data); + getAdapter().swapCursor(CursorAdapter.KeyCursor.wrap(data)); // The list should now be shown. if (isResumed()) { - setListShown(true); + showList(true); } else { - setListShownNoAnimation(true); + showList(false); } } @@ -188,35 +148,31 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen // 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); + getAdapter().swapCursor(null); } - private class SecretKeyCursorAdapter extends SelectKeyCursorAdapter { - - public SecretKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) { - super(context, c, flags, listView); - } - - @Override - protected void initIndex(Cursor cursor) { - super.initIndex(cursor); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - super.bindView(view, context, cursor); - ViewHolderItem h = (ViewHolderItem) view.getTag(); - - h.selected.setVisibility(View.GONE); - - boolean enabled = false; - if ((Boolean) h.statusIcon.getTag()) { - h.statusIcon.setVisibility(View.GONE); - enabled = true; - } - h.setEnabled(enabled); - } + @Override + public void onCreateKeyDummyClicked() { + OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(mPrefUid); + Intent intent = new Intent(getActivity(), CreateKeyActivity.class); + intent.putExtra(CreateKeyActivity.EXTRA_NAME, userIdSplit.name); + intent.putExtra(CreateKeyActivity.EXTRA_EMAIL, userIdSplit.email); + getActivity().startActivityForResult(intent, SelectSignKeyIdActivity.REQUEST_CODE_CREATE_KEY); } + @Override + public void onSelectKeyItemClicked(long masterKeyId) { + Uri allowedKeysUri = mDataUri.buildUpon() + .appendPath(KeychainContract.PATH_ALLOWED_KEYS) + .build(); + + mApiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId); + mResult.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId); + + Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri); + + getActivity().setResult(Activity.RESULT_OK, mResult); + getActivity().finish(); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectEncryptKeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectEncryptKeyAdapter.java new file mode 100644 index 000000000..349d8d6f3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectEncryptKeyAdapter.java @@ -0,0 +1,310 @@ +package org.sufficientlysecure.keychain.remote.ui.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; + +import org.openintents.openpgp.util.OpenPgpUtils; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.ui.adapter.KeyCursorAdapter; +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.CursorAdapter; + +import java.util.ArrayList; +import java.util.Arrays; + +public class SelectEncryptKeyAdapter extends KeyCursorAdapter { + + private ArrayList mSelected; + + public SelectEncryptKeyAdapter(Context context, PublicKeyCursor cursor) { + super(context, cursor); + + mSelected = new ArrayList<>(); + } + + private boolean isSelected(int position) { + return mSelected.contains(position); + } + + private void select(int position) { + if(!isSelected(position)) { + mSelected.add(position); + notifyItemChanged(position); + } + } + + private void switchSelected(int position) { + int index = mSelected.indexOf(position); + if(index < 0) { + mSelected.add(position); + } else { + mSelected.remove(index); + } + + notifyItemChanged(position); + } + + public long[] getMasterKeyIds() { + long[] selected = new long[mSelected.size()]; + for(int i = 0; i < selected.length; i++) { + int position = mSelected.get(i); + if(!moveCursor(position)) { + return selected; + } + + selected[i] = getCursor().getKeyId(); + } + + return selected; + } + + public void preselectMasterKeyIds(long[] keyIds) { + if(keyIds != null) { + int count = 0; + for(int i = 0; i < getItemCount(); i++) { + if(!moveCursor(i)) { + continue; + } + + long id = getCursor().getKeyId(); + for(int l = 0; l < keyIds.length; l++) { + if(id == keyIds[l]) { + select(i); count ++; + break; + } + } + + if(count >= keyIds.length) { + return; + } + } + } + } + + public String[] getRawUserIds() { + String[] selected = new String[mSelected.size()]; + for(int i = 0; i < selected.length; i++) { + int position = mSelected.get(i); + if(!moveCursor(position)) { + return selected; + } + + selected[i] = getCursor().getRawUserId(); + } + + return selected; + } + + public OpenPgpUtils.UserId[] getUserIds() { + OpenPgpUtils.UserId[] selected = new OpenPgpUtils.UserId[mSelected.size()]; + for(int i = 0; i < selected.length; i++) { + int position = mSelected.get(i); + if(!moveCursor(position)) { + return selected; + } + + selected[i] = getCursor().getUserId(); + } + + return selected; + } + + @Override + public void onContentChanged() { + mSelected.clear(); + super.onContentChanged(); + } + + @Override + public void onBindViewHolder(EncryptKeyItemHolder holder, PublicKeyCursor cursor, String query) { + holder.bind(cursor, query, isSelected(holder.getAdapterPosition())); + } + + @Override + public EncryptKeyItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new EncryptKeyItemHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.select_encrypt_key_item, parent, false)); + } + + class EncryptKeyItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + private TextView mUserIdText; + private TextView mUserIdRestText; + private TextView mCreationText; + private ImageView mStatusIcon; + private CheckBox mChecked; + + public EncryptKeyItemHolder(View itemView) { + super(itemView); + itemView.setOnClickListener(this); + + mUserIdText = (TextView) itemView.findViewById(R.id.select_key_item_name); + mUserIdRestText = (TextView) itemView.findViewById(R.id.select_key_item_email); + mCreationText = (TextView) itemView.findViewById(R.id.select_key_item_creation); + mStatusIcon = (ImageView) itemView.findViewById(R.id.select_key_item_status_icon); + mChecked = (CheckBox) itemView.findViewById(R.id.selected); + } + + public void bind(PublicKeyCursor cursor, String query, boolean selected) { + Highlighter highlighter = new Highlighter(itemView.getContext(), query); + Context context = itemView.getContext(); + + { // set name and stuff, common to both key types + OpenPgpUtils.UserId userIdSplit = cursor.getUserId(); + if (userIdSplit.name != null) { + mUserIdText.setText(highlighter.highlight(userIdSplit.name)); + } else { + mUserIdText.setText(R.string.user_id_no_name); + } + if (userIdSplit.email != null) { + mUserIdRestText.setText(highlighter.highlight(userIdSplit.email)); + mUserIdRestText.setVisibility(View.VISIBLE); + } else { + mUserIdRestText.setVisibility(View.GONE); + } + } + + boolean enabled; + { // set edit button and status, specific by key type. Note: order is important! + int textColor; + if (cursor.isRevoked()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + null, + KeyFormattingUtils.State.REVOKED, + R.color.key_flag_gray + ); + + mStatusIcon.setVisibility(View.VISIBLE); + + enabled = false; + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (cursor.isExpired()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + null, + KeyFormattingUtils.State.EXPIRED, + R.color.key_flag_gray + ); + + mStatusIcon.setVisibility(View.VISIBLE); + + enabled = false; + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (!cursor.hasEncrypt()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + KeyFormattingUtils.State.UNAVAILABLE + ); + + mStatusIcon.setVisibility(View.VISIBLE); + + enabled = false; + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (cursor.isVerified()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + KeyFormattingUtils.State.VERIFIED + ); + + mStatusIcon.setVisibility(View.VISIBLE); + + enabled = true; + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } else { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + KeyFormattingUtils.State.UNVERIFIED + ); + + mStatusIcon.setVisibility(View.VISIBLE); + + enabled = true; + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } + + mUserIdText.setTextColor(textColor); + mUserIdRestText.setTextColor(textColor); + + if (cursor.hasDuplicate()) { + String dateTime = DateUtils.formatDateTime(context, + cursor.getCreationTime(), + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + mCreationText.setText(context.getString(R.string.label_key_created, + dateTime)); + mCreationText.setTextColor(textColor); + mCreationText.setVisibility(View.VISIBLE); + } else { + mCreationText.setVisibility(View.GONE); + } + } + + itemView.setEnabled(enabled); + itemView.setClickable(enabled); + mChecked.setChecked(enabled && selected); + + } + + @Override + public void onClick(View v) { + switchSelected(getAdapterPosition()); + } + } + + public static class PublicKeyCursor extends CursorAdapter.KeyCursor { + public static final String[] PROJECTION; + + static { + ArrayList arr = new ArrayList<>(); + arr.addAll(Arrays.asList(KeyCursor.PROJECTION)); + arr.addAll(Arrays.asList( + KeychainContract.KeyRings.HAS_ENCRYPT, + KeychainContract.KeyRings.VERIFIED + )); + + PROJECTION = arr.toArray(new String[arr.size()]); + } + + public static PublicKeyCursor wrap(Cursor cursor) { + if (cursor != null) { + return new PublicKeyCursor(cursor); + } else { + return null; + } + } + + private PublicKeyCursor(Cursor cursor) { + super(cursor); + } + + public boolean hasEncrypt() { + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ENCRYPT); + return getInt(index) != 0; + } + + public boolean isVerified() { + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.VERIFIED); + return getInt(index) != 0; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectSignKeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectSignKeyAdapter.java new file mode 100644 index 000000000..9577f2ef7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectSignKeyAdapter.java @@ -0,0 +1,205 @@ +package org.sufficientlysecure.keychain.remote.ui.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.openintents.openpgp.util.OpenPgpUtils; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.adapter.KeyCursorAdapter; +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.CursorAdapter; + +public class SelectSignKeyAdapter extends KeyCursorAdapter { + private static final int VIEW_TYPE_KEY = 0; + private static final int VIEW_TYPE_DUMMY = 1; + + private SelectSignKeyListener mListener; + + public SelectSignKeyAdapter(Context context, Cursor cursor) { + super(context, KeyCursor.wrap(cursor)); + } + + public void setListener(SelectSignKeyListener listener) { + mListener = listener; + } + + @Override + public int getItemCount() { + return super.getItemCount() + 1; + } + + @Override + public long getItemId(int pos) { + if(pos < super.getItemCount()) { + return super.getItemId(pos); + } else { + return 0L; + } + } + + @Override + public long getIdFromCursor(KeyCursor keyCursor) { + return keyCursor.getKeyId(); + } + + @Override + public int getItemViewType(int position) { + return position == getItemCount() -1 ? + VIEW_TYPE_DUMMY : VIEW_TYPE_KEY; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case VIEW_TYPE_KEY: + return new SignKeyItemHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.select_sign_key_item, parent, false)); + + case VIEW_TYPE_DUMMY: + return new DummyViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.select_dummy_key_item, parent, false)); + + default: + return null; + } + + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if(holder.getItemViewType() == VIEW_TYPE_KEY) { + super.onBindViewHolder(holder, position - 1); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, KeyCursor cursor, String query) { + ((SignKeyItemHolder)holder).bind(cursor, query); + } + + private class DummyViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener{ + + public DummyViewHolder(View itemView) { + super(itemView); + itemView.setClickable(true); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onCreateKeyDummyClicked(); + } + } + } + + private class SignKeyItemHolder extends RecyclerView.ViewHolder + implements View.OnClickListener { + + private TextView mUserIdText; + private TextView mUserIdRestText; + private TextView mCreationText; + private ImageView mStatusIcon; + + public SignKeyItemHolder(View itemView) { + super(itemView); + + mUserIdText = (TextView) itemView.findViewById(R.id.select_key_item_name); + mUserIdRestText = (TextView) itemView.findViewById(R.id.select_key_item_email); + mCreationText = (TextView) itemView.findViewById(R.id.select_key_item_creation); + mStatusIcon = (ImageView) itemView.findViewById(R.id.select_key_item_status_icon); + } + + public void bind(KeyCursor cursor, String query) { + Highlighter highlighter = new Highlighter(itemView.getContext(), query); + Context context = itemView.getContext(); + + { // set name and stuff, common to both key types + OpenPgpUtils.UserId userIdSplit = cursor.getUserId(); + if (userIdSplit.name != null) { + mUserIdText.setText(highlighter.highlight(userIdSplit.name)); + } else { + mUserIdText.setText(R.string.user_id_no_name); + } + if (userIdSplit.email != null) { + mUserIdRestText.setText(highlighter.highlight(userIdSplit.email)); + mUserIdRestText.setVisibility(View.VISIBLE); + } else { + mUserIdRestText.setVisibility(View.GONE); + } + } + + { // set edit button and status, specific by key type. Note: order is important! + int textColor; + if (cursor.isRevoked()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + null, + KeyFormattingUtils.State.REVOKED, + R.color.key_flag_gray + ); + + mStatusIcon.setVisibility(View.VISIBLE); + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (cursor.isExpired()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + null, + KeyFormattingUtils.State.EXPIRED, + R.color.key_flag_gray + ); + + mStatusIcon.setVisibility(View.VISIBLE); + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else { + mStatusIcon.setVisibility(View.GONE); + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } + + mUserIdText.setTextColor(textColor); + mUserIdRestText.setTextColor(textColor); + + if (cursor.hasDuplicate()) { + String dateTime = DateUtils.formatDateTime(context, + cursor.getCreationTime(), + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + mCreationText.setText(context.getString(R.string.label_key_created, + dateTime)); + mCreationText.setTextColor(textColor); + mCreationText.setVisibility(View.VISIBLE); + } else { + mCreationText.setVisibility(View.GONE); + } + } + + } + + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onSelectKeyItemClicked(getItemId()); + } + } + } + + public interface SelectSignKeyListener { + void onCreateKeyDummyClicked(); + void onSelectKeyItemClicked(long masterKeyId); + } +} 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 4775bcb71..596a715af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -70,6 +70,7 @@ import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.adapter.KeySectionedListAdapter; +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerFragment; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Log; @@ -311,8 +312,8 @@ public class KeyListFragment extends RecyclerFragment // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. return new CursorLoader(getActivity(), uri, - KeySectionedListAdapter.KeyCursor.PROJECTION, null, null, - KeySectionedListAdapter.KeyCursor.ORDER); + KeySectionedListAdapter.KeyListCursor.PROJECTION, null, null, + KeySectionedListAdapter.KeyListCursor.ORDER); } @Override @@ -320,7 +321,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); - getAdapter().swapCursor(KeySectionedListAdapter.KeyCursor.wrap(data)); + getAdapter().swapCursor(KeySectionedListAdapter.KeyListCursor.wrap(data)); // end action mode, if any if (mActionMode != null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java deleted file mode 100644 index 6f48b7455..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann - * Copyright (C) 2010-2014 Thialfihar - * - * 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 . - */ - -package org.sufficientlysecure.keychain.ui; - -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; -import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; - -import java.util.Vector; - -public class SelectPublicKeyFragment extends ListFragmentWorkaround implements TextWatcher, - LoaderManager.LoaderCallbacks { - public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids"; - - private SelectKeyCursorAdapter mAdapter; - private EditText mSearchView; - private long mSelectedMasterKeyIds[]; - private String mQuery; - - // copied from ListFragment - static final int INTERNAL_EMPTY_ID = 0x00ff0001; - static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002; - static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003; - // added for search view - static final int SEARCH_ID = 0x00ff0004; - - /** - * Creates new instance of this fragment - */ - public static SelectPublicKeyFragment newInstance(long[] preselectedKeyIds) { - SelectPublicKeyFragment frag = new SelectPublicKeyFragment(); - Bundle args = new Bundle(); - - args.putLongArray(ARG_PRESELECTED_KEY_IDS, preselectedKeyIds); - - frag.setArguments(args); - - return frag; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mSelectedMasterKeyIds = getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS); - } - - /** - * Copied from ListFragment and added EditText for search on top of list. - * We do not use a custom layout here, because this breaks the progress bar functionality - * of ListFragment. - * - * @param inflater - * @param container - * @param savedInstanceState - * @return - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - final Context context = getActivity(); - - FrameLayout root = new FrameLayout(context); - - // ------------------------------------------------------------------ - - LinearLayout pframe = new LinearLayout(context); - pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID); - pframe.setOrientation(LinearLayout.VERTICAL); - pframe.setVisibility(View.GONE); - pframe.setGravity(Gravity.CENTER); - - ProgressBar progress = new ProgressBar(context, null, - android.R.attr.progressBarStyleLarge); - pframe.addView(progress, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - root.addView(pframe, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - // ------------------------------------------------------------------ - - FrameLayout lframe = new FrameLayout(context); - lframe.setId(INTERNAL_LIST_CONTAINER_ID); - - TextView tv = new TextView(getActivity()); - tv.setId(INTERNAL_EMPTY_ID); - tv.setGravity(Gravity.CENTER); - lframe.addView(tv, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - // Added for search view: linearLayout, mSearchView - LinearLayout linearLayout = new LinearLayout(context); - linearLayout.setOrientation(LinearLayout.VERTICAL); - - mSearchView = new EditText(context); - mSearchView.setId(SEARCH_ID); - mSearchView.setHint(R.string.menu_search); - mSearchView.setCompoundDrawablesWithIntrinsicBounds( - getResources().getDrawable(R.drawable.ic_search_grey_24dp), null, null, null); - - linearLayout.addView(mSearchView, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - ListView lv = new ListView(getActivity()); - lv.setId(android.R.id.list); - lv.setDrawSelectorOnTop(false); - linearLayout.addView(lv, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - lframe.addView(linearLayout, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - root.addView(lframe, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - // ------------------------------------------------------------------ - - root.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - return root; - } - - /** - * Define Adapter and Loader on create of Activity - */ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - - // 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)); - - mSearchView.addTextChangedListener(this); - - mAdapter = new SelectPublicKeyCursorAdapter(getActivity(), null, 0, getListView()); - - setListAdapter(mAdapter); - - // Start out with a progress indicator. - setListShown(false); - - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - getLoaderManager().initLoader(0, null, this); - } - - /** - * Selects items based on master key ids in list view - * - * @param masterKeyIds - */ - private void preselectMasterKeyIds(long[] masterKeyIds) { - if (masterKeyIds != null) { - for (int i = 0; i < getListView().getCount(); ++i) { - long keyId = mAdapter.getMasterKeyId(i); - for (long masterKeyId : masterKeyIds) { - if (keyId == masterKeyId) { - getListView().setItemChecked(i, true); - break; - } - } - } - } - } - - /** - * Returns all selected master key ids - * - * @return - */ - public long[] getSelectedMasterKeyIds() { - // mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key - // ids! - Vector vector = new Vector<>(); - for (int i = 0; i < getListView().getCount(); ++i) { - if (getListView().isItemChecked(i)) { - vector.add(mAdapter.getMasterKeyId(i)); - } - } - - // convert to long array - long[] selectedMasterKeyIds = new long[vector.size()]; - for (int i = 0; i < vector.size(); ++i) { - selectedMasterKeyIds[i] = vector.get(i); - } - - return selectedMasterKeyIds; - } - - /** - * Returns all selected user ids - * - * @return - */ - public String[] getSelectedUserIds() { - Vector userIds = new Vector<>(); - for (int i = 0; i < getListView().getCount(); ++i) { - if (getListView().isItemChecked(i)) { - userIds.add(mAdapter.getUserId(i)); - } - } - - // make empty array to not return null - String userIdArray[] = new String[0]; - return userIds.toArray(userIdArray); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); - - // These are the rows that we will retrieve. - String[] projection = new String[]{ - KeyRings._ID, - KeyRings.MASTER_KEY_ID, - KeyRings.USER_ID, - KeyRings.IS_EXPIRED, - KeyRings.IS_REVOKED, - KeyRings.HAS_ENCRYPT, - KeyRings.VERIFIED, - KeyRings.HAS_DUPLICATE_USER_ID, - KeyRings.CREATION, - }; - - String inMasterKeyList = null; - if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) { - inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN ("; - for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) { - if (i != 0) { - inMasterKeyList += ", "; - } - inMasterKeyList += DatabaseUtils.sqlEscapeString("" + mSelectedMasterKeyIds[i]); - } - inMasterKeyList += ")"; - } - - String orderBy = KeyRings.USER_ID + " ASC"; - if (inMasterKeyList != null) { - // sort by selected master keys - orderBy = inMasterKeyList + " DESC, " + orderBy; - } - String where = null; - String whereArgs[] = null; - if (mQuery != null) { - String[] words = mQuery.trim().split("\\s+"); - whereArgs = new String[words.length]; - for (int i = 0; i < words.length; ++i) { - if (where == null) { - where = ""; - } else { - where += " AND "; - } - where += KeyRings.USER_ID + " LIKE ?"; - whereArgs[i] = "%" + words[i] + "%"; - } - } - - // 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, where, whereArgs, orderBy); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - mAdapter.setSearchQuery(mQuery); - mAdapter.swapCursor(data); - - // The list should now be shown. - if (isResumed()) { - setListShown(true); - } else { - setListShownNoAnimation(true); - } - - // preselect given master keys - preselectMasterKeyIds(mSelectedMasterKeyIds); - } - - @Override - public void onLoaderReset(Loader 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); - } - - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { - - } - - @Override - public void afterTextChanged(Editable editable) { - mQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null; - getLoaderManager().restartLoader(0, null, this); - } - - private class SelectPublicKeyCursorAdapter extends SelectKeyCursorAdapter { - - private int mIndexHasEncrypt, mIndexIsVerified; - - public SelectPublicKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) { - super(context, c, flags, listView); - } - - @Override - protected void initIndex(Cursor cursor) { - super.initIndex(cursor); - if (cursor != null) { - mIndexHasEncrypt = cursor.getColumnIndexOrThrow(KeyRings.HAS_ENCRYPT); - mIndexIsVerified = cursor.getColumnIndexOrThrow(KeyRings.VERIFIED); - } - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - super.bindView(view, context, cursor); - ViewHolderItem h = (SelectKeyCursorAdapter.ViewHolderItem) view.getTag(); - - // We care about the checkbox - h.selected.setVisibility(View.VISIBLE); - // the getListView works because this is not a static subclass! - h.selected.setChecked(getListView().isItemChecked(cursor.getPosition())); - - boolean enabled = false; - if((Boolean) h.statusIcon.getTag()) { - // Check if key is viable for our purposes - if (cursor.getInt(mIndexHasEncrypt) == 0) { - h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.UNAVAILABLE); - enabled = false; - } else if (cursor.getInt(mIndexIsVerified) != 0) { - h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.VERIFIED); - enabled = true; - } else { - h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.UNVERIFIED); - enabled = true; - } - } - - h.setEnabled(enabled); - } - - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java index eb75080a8..4370f8753 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java @@ -2,13 +2,11 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; import android.database.Cursor; -import android.database.CursorWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import java.util.HashMap; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.R; @@ -17,8 +15,11 @@ import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; import org.sufficientlysecure.keychain.ui.util.adapter.SectionCursorAdapter; +import java.util.ArrayList; +import java.util.Arrays; public class CertSectionedListAdapter extends SectionCursorAdapter { @@ -65,7 +66,9 @@ public class CertSectionedListAdapter extends SectionCursorAdapter projection = new ArrayList<>(); + projection.addAll(Arrays.asList(AbstractCursor.PROJECTION)); + projection.addAll(Arrays.asList( + KeychainContract.Certs.MASTER_KEY_ID, + KeychainContract.Certs.VERIFIED, + KeychainContract.Certs.TYPE, + KeychainContract.Certs.RANK, + KeychainContract.Certs.KEY_ID_CERTIFIER, + KeychainContract.Certs.USER_ID, + KeychainContract.Certs.SIGNER_UID + )); + + CERTS_PROJECTION = projection.toArray(new String[projection.size()]); + } public static final String CERTS_SORT_ORDER = KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.RANK + " ASC, " @@ -167,27 +176,8 @@ public class CertSectionedListAdapter extends SectionCursorAdapter mColumnIndices; - - /** - * Creates a cursor wrapper. - * - * @param cursor The underlying cursor to wrap. - */ private CertCursor(Cursor cursor) { super(cursor); - mColumnIndices = new HashMap<>(cursor.getColumnCount() * 4 / 3, 0.75f); - } - - @Override - public void close() { - mColumnIndices.clear(); - super.close(); - } - - public int getEntryId() { - int index = getColumnIndexOrThrow(KeychainContract.Certs._ID); - return getInt(index); } public long getKeyId() { @@ -232,30 +222,6 @@ public class CertSectionedListAdapter extends SectionCursorAdapter + extends CursorAdapter { + + private String mQuery; + + public KeyCursorAdapter(Context context, C cursor){ + super(context, cursor, 0); + } + + public void setQuery(String query) { + mQuery = query; + } + + @Override + public int getItemViewType(int position) { + moveCursorOrThrow(position); + return getItemViewType(getCursor()); + } + + @Override + public long getIdFromCursor(C keyCursor) { + return keyCursor.getKeyId(); + } + + @Override + public void onBindViewHolder(VH holder, int position) { + moveCursorOrThrow(position); + onBindViewHolder(holder, getCursor(), mQuery); + } + + public int getItemViewType(C keyCursor) { return 0; } + public abstract void onBindViewHolder(VH holder, C cursor, String query); +} 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 4744f01dd..5b06b8cf4 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,7 +2,6 @@ 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.PorterDuff; @@ -19,7 +18,6 @@ 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; @@ -28,10 +26,10 @@ import org.sufficientlysecure.keychain.ui.util.adapter.*; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; -import java.util.Date; +import java.util.Arrays; import java.util.List; -public class KeySectionedListAdapter extends SectionCursorAdapter { private static final short VIEW_ITEM_TYPE_KEY = 0x0; @@ -47,7 +45,7 @@ public class KeySectionedListAdapter extends SectionCursorAdapter(); @@ -71,15 +69,15 @@ public class KeySectionedListAdapter extends SectionCursorAdapter arr = new ArrayList<>(); + arr.addAll(Arrays.asList(KeyCursor.PROJECTION)); + arr.addAll(Arrays.asList( + KeychainContract.KeyRings.VERIFIED, + KeychainContract.KeyRings.HAS_ANY_SECRET, + KeychainContract.KeyRings.FINGERPRINT, + KeychainContract.KeyRings.HAS_ENCRYPT + )); - public static KeyCursor wrap(Cursor cursor) { - if(cursor != null) { - return new KeyCursor(cursor); + PROJECTION = arr.toArray(new String[arr.size()]); + } + + public static KeyListCursor wrap(Cursor cursor) { + if (cursor != null) { + return new KeyListCursor(cursor); } else { return null; } } - /** - * Creates a cursor wrapper. - * - * @param cursor The underlying cursor to wrap. - */ - private KeyCursor(Cursor cursor) { + private KeyListCursor(Cursor cursor) { super(cursor); } - public int getEntryId() { - return getInt(INDEX_ENTRY_ID); - } - - public String getRawUserId() { - return getString(INDEX_USER_ID); - } - - public OpenPgpUtils.UserId getUserId() { - return KeyRing.splitUserId(getRawUserId()); - } - - public long getKeyId() { - return getLong(INDEX_MASTER_KEY_ID); - } - - public boolean hasDuplicate() { - return getLong(INDEX_HAS_DUPLICATE_USER_ID) > 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()); + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ENCRYPT); + return getInt(index) != 0; } public byte[] getRawFingerprint() { - return getBlob(INDEX_FINGERPRINT); + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.FINGERPRINT); + return getBlob(index); } public String getFingerprint() { @@ -578,19 +534,13 @@ public class KeySectionedListAdapter extends SectionCursorAdapter 0; - } - - public boolean isExpired() { - return getInt(INDEX_IS_EXPIRED) > 0; + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ANY_SECRET); + return getInt(index) != 0; } public boolean isVerified() { - return getInt(INDEX_VERIFIED) > 0; + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.VERIFIED); + return getInt(index) > 0; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index 3e94ac728..1f0fddab6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -166,7 +166,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - View view = mInflater.inflate(R.layout.select_key_item, null); + View view = mInflater.inflate(R.layout.select_encrypt_key_item, null); ViewHolderItem holder = new ViewHolderItem(); holder.view = view; holder.mainUserId = (TextView) view.findViewById(R.id.select_key_item_name); 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 e2330c539..6648d0bc5 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 @@ -3,13 +3,26 @@ package org.sufficientlysecure.keychain.ui.util.adapter; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; +import android.database.CursorWrapper; import android.database.DataSetObserver; import android.os.Handler; import android.support.v7.widget.RecyclerView; +import org.openintents.openpgp.util.OpenPgpUtils; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.util.Log; -public abstract class CursorAdapter extends RecyclerView.Adapter { +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; + +public abstract class CursorAdapter + extends RecyclerView.Adapter { public static final String TAG = "CursorAdapter"; private C mCursor; @@ -31,7 +44,7 @@ public abstract class CursorAdapter extends RecyclerView.Adapt /** * Constructor that allows control over auto-requery. It is recommended - * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}. + * you not use this, but instead {@link #CursorAdapter(Context, AbstractCursor, int)}. * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER} * will always be set. * @@ -141,7 +154,7 @@ public abstract class CursorAdapter extends RecyclerView.Adapt */ public long getIdFromCursor(C cursor) { if(cursor != null) { - return cursor.getPosition(); + return cursor.getEntryId(); } else { return RecyclerView.NO_ID; } @@ -193,7 +206,7 @@ public abstract class CursorAdapter extends RecyclerView.Adapt /** * Swap in a new Cursor, returning the old Cursor. Unlike - * {@link #changeCursor(Cursor)}, the returned old Cursor is not + * {@link #changeCursor(AbstractCursor)}, the returned old Cursor is not * closed. * * @param newCursor The new cursor to be used. @@ -281,4 +294,142 @@ public abstract class CursorAdapter extends RecyclerView.Adapt onContentChanged(); } } + + public static abstract class AbstractCursor extends CursorWrapper { + public static final String[] PROJECTION = { "_id" }; + + public static T wrap(Cursor cursor, Class type) { + if (cursor != null) { + try { + Constructor constructor = type.getConstructor(Cursor.class); + return constructor.newInstance(cursor); + } catch (Exception e) { + Log.e(Constants.TAG, "Could not create instance of cursor wrapper!", e); + } + } + + return null; + } + + private HashMap mColumnIndices; + + /** + * Creates a cursor wrapper. + * + * @param cursor The underlying cursor to wrap. + */ + protected AbstractCursor(Cursor cursor) { + super(cursor); + mColumnIndices = new HashMap<>(cursor.getColumnCount() * 4 / 3, 0.75f); + } + + @Override + public void close() { + mColumnIndices.clear(); + super.close(); + } + + public final int getEntryId() { + int index = getColumnIndexOrThrow("_id"); + return getInt(index); + } + + @Override + public final int getColumnIndexOrThrow(String colName) { + Integer colIndex = mColumnIndices.get(colName); + if(colIndex == null) { + colIndex = super.getColumnIndexOrThrow(colName); + mColumnIndices.put(colName, colIndex); + } else if (colIndex < 0){ + throw new IllegalArgumentException("Could not get column index for name: \"" + colName + "\""); + } + + return colIndex; + } + + @Override + public final int getColumnIndex(String colName) { + Integer colIndex = mColumnIndices.get(colName); + if(colIndex == null) { + colIndex = super.getColumnIndex(colName); + mColumnIndices.put(colName, colIndex); + } + + return colIndex; + } + } + + public static class KeyCursor extends AbstractCursor { + public static final String[] PROJECTION; + + static { + ArrayList arr = new ArrayList<>(); + arr.addAll(Arrays.asList(AbstractCursor.PROJECTION)); + arr.addAll(Arrays.asList( + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.IS_REVOKED, + KeychainContract.KeyRings.IS_EXPIRED, + KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID, + KeychainContract.KeyRings.CREATION + )); + + PROJECTION = arr.toArray(new String[arr.size()]); + } + + public static KeyCursor wrap(Cursor cursor) { + if (cursor != null) { + return new KeyCursor(cursor); + } else { + return null; + } + } + + /** + * Creates a cursor wrapper. + * + * @param cursor The underlying cursor to wrap. + */ + protected KeyCursor(Cursor cursor) { + super(cursor); + } + + public long getKeyId() { + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.MASTER_KEY_ID); + return getLong(index); + } + + public String getRawUserId() { + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.USER_ID); + return getString(index); + } + + public OpenPgpUtils.UserId getUserId() { + return KeyRing.splitUserId(getRawUserId()); + } + + public boolean hasDuplicate() { + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID); + return getLong(index) > 0L; + } + + public boolean isRevoked() { + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.IS_REVOKED); + return getInt(index) > 0; + } + + public boolean isExpired() { + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.IS_EXPIRED); + return getInt(index) > 0; + } + + public long getCreationTime() { + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.CREATION); + return getLong(index) * 1000; + } + + public Date getCreationDate() { + return new Date(getCreationTime()); + } + } } \ No newline at end of file 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 4f297ecbf..c1842e052 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 @@ -17,8 +17,8 @@ import java.util.Objects; * @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"; @@ -40,6 +40,8 @@ public abstract class SectionCursorAdapter comparator) { super(context, cursor, flags); + + setHasStableIds(false); // because we have additional section items setSectionComparator(comparator); } @@ -122,6 +124,7 @@ public abstract class SectionCursorAdapter extends Fragment { - private static final int INTERNAL_LIST_VIEW_ID = android.R.id.list; - private static final int INTERNAL_EMPTY_VIEW_ID = android.R.id.empty; - private static final int INTERNAL_LIST_CONTAINER_ID = android.R.id.widget_frame; - private static final int INTERNAL_PROGRESS_CONTAINER_ID = android.R.id.progress; + protected static final int INTERNAL_LIST_VIEW_ID = android.R.id.list; + protected static final int INTERNAL_EMPTY_VIEW_ID = android.R.id.empty; + protected static final int INTERNAL_LIST_CONTAINER_ID = android.R.id.widget_frame; + protected static final int INTERNAL_PROGRESS_CONTAINER_ID = android.R.id.progress; private final Handler handler = new Handler(); private final Runnable requestFocus = new Runnable() { @@ -144,7 +145,6 @@ public class RecyclerFragment extends Fragment { listContainer.addView(listView, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - root.addView(listContainer, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); @@ -299,6 +299,16 @@ public class RecyclerFragment extends Fragment { setListShown(true, animated); } + public void setEmptyText(String text) { + ensureList(); + if(emptyView instanceof TextView) { + ((TextView) emptyView).setText(text); + } else { + Log.e(Constants.TAG, "Cannot set empty text on a view that is null" + + "or not an instance of android.view.View!"); + } + } + private void setListShown(boolean shown, boolean animated) { ensureList(); diff --git a/OpenKeychain/src/main/res/layout/api_select_sign_key_activity.xml b/OpenKeychain/src/main/res/layout/api_select_sign_key_activity.xml index 981e01dd1..bc88baae4 100644 --- a/OpenKeychain/src/main/res/layout/api_select_sign_key_activity.xml +++ b/OpenKeychain/src/main/res/layout/api_select_sign_key_activity.xml @@ -11,26 +11,16 @@ android:layout_below="@id/toolbar_include" android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="match_parent"> - - - - - + android:layout_height="match_parent" + android:padding="8dp"> @@ -60,33 +50,7 @@ + android:layout_height="match_parent" /> - - - - - - - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/select_dummy_key_item.xml b/OpenKeychain/src/main/res/layout/select_dummy_key_item.xml new file mode 100644 index 000000000..c217d018c --- /dev/null +++ b/OpenKeychain/src/main/res/layout/select_dummy_key_item.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/select_key_item.xml b/OpenKeychain/src/main/res/layout/select_encrypt_key_item.xml similarity index 87% rename from OpenKeychain/src/main/res/layout/select_key_item.xml rename to OpenKeychain/src/main/res/layout/select_encrypt_key_item.xml index aff0a8215..69e3f8cc0 100644 --- a/OpenKeychain/src/main/res/layout/select_key_item.xml +++ b/OpenKeychain/src/main/res/layout/select_encrypt_key_item.xml @@ -1,11 +1,14 @@ - + android:maxLines="1"> diff --git a/OpenKeychain/src/main/res/layout/select_sign_key_item.xml b/OpenKeychain/src/main/res/layout/select_sign_key_item.xml new file mode 100644 index 000000000..e27eb2518 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/select_sign_key_item.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + \ No newline at end of file