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