mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-16 05:45:04 -05:00
can search openkeychain, retrieve & install & use keys from there
This commit is contained in:
parent
e0a0bf04ee
commit
e663dadc32
1
.gitignore
vendored
1
.gitignore
vendored
@ -31,3 +31,4 @@ pom.xml.*
|
|||||||
|
|
||||||
#OS Specific
|
#OS Specific
|
||||||
[Tt]humbs.db
|
[Tt]humbs.db
|
||||||
|
.DS_Store
|
||||||
|
@ -56,6 +56,8 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|||||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
||||||
import org.sufficientlysecure.keychain.util.HkpKeyServer;
|
import org.sufficientlysecure.keychain.util.HkpKeyServer;
|
||||||
import org.sufficientlysecure.keychain.util.InputData;
|
import org.sufficientlysecure.keychain.util.InputData;
|
||||||
|
import org.sufficientlysecure.keychain.util.KeyServer;
|
||||||
|
import org.sufficientlysecure.keychain.util.KeybaseKeyServer;
|
||||||
import org.sufficientlysecure.keychain.util.KeychainServiceListener;
|
import org.sufficientlysecure.keychain.util.KeychainServiceListener;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||||
@ -103,6 +105,7 @@ public class KeychainIntentService extends IntentService
|
|||||||
|
|
||||||
public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
|
public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
|
||||||
public static final String ACTION_DOWNLOAD_AND_IMPORT_KEYS = Constants.INTENT_PREFIX + "QUERY_KEYRING";
|
public static final String ACTION_DOWNLOAD_AND_IMPORT_KEYS = Constants.INTENT_PREFIX + "QUERY_KEYRING";
|
||||||
|
public static final String ACTION_IMPORT_KEYBASE_KEYS = Constants.INTENT_PREFIX + "DOWNLOAD_KEYBASE";
|
||||||
|
|
||||||
public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
|
public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
|
||||||
|
|
||||||
@ -739,6 +742,56 @@ public class KeychainIntentService extends IntentService
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
sendErrorToHandler(e);
|
sendErrorToHandler(e);
|
||||||
}
|
}
|
||||||
|
} else if (ACTION_IMPORT_KEYBASE_KEYS.equals(action)) {
|
||||||
|
ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST);
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeybaseKeyServer server = new KeybaseKeyServer();
|
||||||
|
for (ImportKeysListEntry entry : entries) {
|
||||||
|
// the keybase handle is in userId(1)
|
||||||
|
String username = entry.getUserIds().get(1);
|
||||||
|
byte[] downloadedKeyBytes = server.get(username).getBytes();
|
||||||
|
|
||||||
|
// create PGPKeyRing object based on downloaded armored key
|
||||||
|
PGPKeyRing downloadedKey = null;
|
||||||
|
BufferedInputStream bufferedInput =
|
||||||
|
new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes));
|
||||||
|
if (bufferedInput.available() > 0) {
|
||||||
|
InputStream in = PGPUtil.getDecoderStream(bufferedInput);
|
||||||
|
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
|
||||||
|
|
||||||
|
// get first object in block
|
||||||
|
Object obj;
|
||||||
|
if ((obj = objectFactory.nextObject()) != null) {
|
||||||
|
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||||
|
|
||||||
|
if (obj instanceof PGPKeyRing) {
|
||||||
|
downloadedKey = (PGPKeyRing) obj;
|
||||||
|
} else {
|
||||||
|
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save key bytes in entry object for doing the
|
||||||
|
// actual import afterwards
|
||||||
|
entry.setBytes(downloadedKey.getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent importIntent = new Intent(this, KeychainIntentService.class);
|
||||||
|
importIntent.setAction(ACTION_IMPORT_KEYRING);
|
||||||
|
Bundle importData = new Bundle();
|
||||||
|
importData.putParcelableArrayList(IMPORT_KEY_LIST, entries);
|
||||||
|
importIntent.putExtra(EXTRA_DATA, importData);
|
||||||
|
importIntent.putExtra(EXTRA_MESSENGER, mMessenger);
|
||||||
|
|
||||||
|
// now import it with this service
|
||||||
|
onHandleIntent(importIntent);
|
||||||
|
|
||||||
|
// result is handled in ACTION_IMPORT_KEYRING
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendErrorToHandler(e);
|
||||||
|
}
|
||||||
} else if (ACTION_DOWNLOAD_AND_IMPORT_KEYS.equals(action)) {
|
} else if (ACTION_DOWNLOAD_AND_IMPORT_KEYS.equals(action)) {
|
||||||
try {
|
try {
|
||||||
ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST);
|
ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST);
|
||||||
|
@ -62,6 +62,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
|
|||||||
+ "IMPORT_KEY_FROM_KEYSERVER";
|
+ "IMPORT_KEY_FROM_KEYSERVER";
|
||||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX
|
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX
|
||||||
+ "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN";
|
+ "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN";
|
||||||
|
public static final String ACTION_IMPORT_KEY_FROM_KEYBASE = Constants.INTENT_PREFIX
|
||||||
|
+ "IMPORT_KEY_FROM_KEYBASE";
|
||||||
|
|
||||||
// Actions for internal use only:
|
// Actions for internal use only:
|
||||||
public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
|
public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
|
||||||
@ -92,13 +94,15 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
|
|||||||
ImportKeysFileFragment.class,
|
ImportKeysFileFragment.class,
|
||||||
ImportKeysQrCodeFragment.class,
|
ImportKeysQrCodeFragment.class,
|
||||||
ImportKeysClipboardFragment.class,
|
ImportKeysClipboardFragment.class,
|
||||||
ImportKeysNFCFragment.class
|
ImportKeysNFCFragment.class,
|
||||||
|
ImportKeysKeybaseFragment.class
|
||||||
};
|
};
|
||||||
private static final int NAV_SERVER = 0;
|
private static final int NAV_SERVER = 0;
|
||||||
private static final int NAV_FILE = 1;
|
private static final int NAV_FILE = 1;
|
||||||
private static final int NAV_QR_CODE = 2;
|
private static final int NAV_QR_CODE = 2;
|
||||||
private static final int NAV_CLIPBOARD = 3;
|
private static final int NAV_CLIPBOARD = 3;
|
||||||
private static final int NAV_NFC = 4;
|
private static final int NAV_NFC = 4;
|
||||||
|
private static final int NAV_KEYBASE = 5;
|
||||||
|
|
||||||
private int mCurrentNavPosition = -1;
|
private int mCurrentNavPosition = -1;
|
||||||
|
|
||||||
@ -236,6 +240,12 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
|
|||||||
// NOTE: this only displays the appropriate fragment, no actions are taken
|
// NOTE: this only displays the appropriate fragment, no actions are taken
|
||||||
loadNavFragment(NAV_NFC, null);
|
loadNavFragment(NAV_NFC, null);
|
||||||
|
|
||||||
|
// no immediate actions!
|
||||||
|
startListFragment(savedInstanceState, null, null, null);
|
||||||
|
} else if (ACTION_IMPORT_KEY_FROM_KEYBASE.equals(action)) {
|
||||||
|
// NOTE: this only displays the appropriate fragment, no actions are taken
|
||||||
|
loadNavFragment(NAV_KEYBASE, null);
|
||||||
|
|
||||||
// no immediate actions!
|
// no immediate actions!
|
||||||
startListFragment(savedInstanceState, null, null, null);
|
startListFragment(savedInstanceState, null, null, null);
|
||||||
} else {
|
} else {
|
||||||
@ -340,8 +350,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
|
|||||||
startListFragment(savedInstanceState, null, null, query);
|
startListFragment(savedInstanceState, null, null, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadCallback(byte[] importData, Uri dataUri, String serverQuery, String keyServer) {
|
public void loadCallback(byte[] importData, Uri dataUri, String serverQuery, String keyServer, String keybaseQuery) {
|
||||||
mListFragment.loadNew(importData, dataUri, serverQuery, keyServer);
|
mListFragment.loadNew(importData, dataUri, serverQuery, keyServer, keybaseQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -449,6 +459,31 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
|
|||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
startService(intent);
|
startService(intent);
|
||||||
|
} else if (mListFragment.getKeybaseQuery() != null) {
|
||||||
|
// Send all information needed to service to query keys in other thread
|
||||||
|
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||||
|
|
||||||
|
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYBASE_KEYS);
|
||||||
|
|
||||||
|
// fill values for this action
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
|
||||||
|
// get selected key entries
|
||||||
|
ArrayList<ImportKeysListEntry> selectedEntries = mListFragment.getSelectedData();
|
||||||
|
data.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST, selectedEntries);
|
||||||
|
|
||||||
|
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||||
|
|
||||||
|
// Create a new Messenger for the communication back
|
||||||
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
|
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
|
// show progress dialog
|
||||||
|
saveHandler.showProgressDialog(this);
|
||||||
|
|
||||||
|
// start service with intent
|
||||||
|
startService(intent);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
AppMsg.makeText(this, R.string.error_nothing_import, AppMsg.STYLE_ALERT).show();
|
AppMsg.makeText(this, R.string.error_nothing_import, AppMsg.STYLE_ALERT).show();
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ public class ImportKeysClipboardFragment extends Fragment {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mImportActivity.loadCallback(sendText.getBytes(), null, null, null);
|
mImportActivity.loadCallback(sendText.getBytes(), null, null, null, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ public class ImportKeysFileFragment extends Fragment {
|
|||||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
|
|
||||||
// load data
|
// load data
|
||||||
mImportActivity.loadCallback(null, data.getData(), null, null);
|
mImportActivity.loadCallback(null, data.getData(), null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import public keys from the Keybase.io directory. First cut: just raw search.
|
||||||
|
* TODO: make a pick list of the people you’re following on keybase
|
||||||
|
*/
|
||||||
|
public class ImportKeysKeybaseFragment extends Fragment {
|
||||||
|
|
||||||
|
private ImportKeysActivity mImportActivity;
|
||||||
|
private BootstrapButton mSearchButton;
|
||||||
|
private EditText mQueryEditText;
|
||||||
|
|
||||||
|
public static final String ARG_QUERY = "query";
|
||||||
|
public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new instance of this fragment
|
||||||
|
*/
|
||||||
|
public static ImportKeysKeybaseFragment newInstance() {
|
||||||
|
ImportKeysKeybaseFragment frag = new ImportKeysKeybaseFragment();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
frag.setArguments(args);
|
||||||
|
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflate the layout for this fragment
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.import_keys_keybase_fragment, container, false);
|
||||||
|
|
||||||
|
mQueryEditText = (EditText) view.findViewById(R.id.import_keybase_query);
|
||||||
|
|
||||||
|
mSearchButton = (BootstrapButton) view.findViewById(R.id.import_keybase_search);
|
||||||
|
mSearchButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
String query = mQueryEditText.getText().toString();
|
||||||
|
search(query);
|
||||||
|
|
||||||
|
// close keyboard after pressing search
|
||||||
|
InputMethodManager imm =
|
||||||
|
(InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(mQueryEditText.getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mQueryEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||||
|
String query = mQueryEditText.getText().toString();
|
||||||
|
search(query);
|
||||||
|
|
||||||
|
// Don't return true to let the keyboard close itself after pressing search
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
mImportActivity = (ImportKeysActivity) getActivity();
|
||||||
|
|
||||||
|
// set displayed values
|
||||||
|
if (getArguments() != null) {
|
||||||
|
if (getArguments().containsKey(ARG_QUERY)) {
|
||||||
|
String query = getArguments().getString(ARG_QUERY);
|
||||||
|
mQueryEditText.setText(query, TextView.BufferType.EDITABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) {
|
||||||
|
mQueryEditText.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void search(String query) {
|
||||||
|
mImportActivity.loadCallback(null, null, null, null, query);
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.helper.Preferences;
|
|||||||
import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
|
import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListKeybaseLoader;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
|
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader;
|
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader;
|
||||||
import org.sufficientlysecure.keychain.util.InputData;
|
import org.sufficientlysecure.keychain.util.InputData;
|
||||||
@ -60,9 +61,11 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
private Uri mDataUri;
|
private Uri mDataUri;
|
||||||
private String mServerQuery;
|
private String mServerQuery;
|
||||||
private String mKeyServer;
|
private String mKeyServer;
|
||||||
|
private String mKeybaseQuery;
|
||||||
|
|
||||||
private static final int LOADER_ID_BYTES = 0;
|
private static final int LOADER_ID_BYTES = 0;
|
||||||
private static final int LOADER_ID_SERVER_QUERY = 1;
|
private static final int LOADER_ID_SERVER_QUERY = 1;
|
||||||
|
private static final int LOADER_ID_KEYBASE = 2;
|
||||||
|
|
||||||
public byte[] getKeyBytes() {
|
public byte[] getKeyBytes() {
|
||||||
return mKeyBytes;
|
return mKeyBytes;
|
||||||
@ -76,6 +79,10 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
return mServerQuery;
|
return mServerQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getKeybaseQuery() {
|
||||||
|
return mKeybaseQuery;
|
||||||
|
}
|
||||||
|
|
||||||
public String getKeyServer() {
|
public String getKeyServer() {
|
||||||
return mKeyServer;
|
return mKeyServer;
|
||||||
}
|
}
|
||||||
@ -148,6 +155,16 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
// give arguments to onCreateLoader()
|
// give arguments to onCreateLoader()
|
||||||
getLoaderManager().initLoader(LOADER_ID_SERVER_QUERY, null, this);
|
getLoaderManager().initLoader(LOADER_ID_SERVER_QUERY, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mKeybaseQuery != null) {
|
||||||
|
// Start out with a progress indicator.
|
||||||
|
setListShown(false);
|
||||||
|
|
||||||
|
// Prepare the loader. Either re-connect with an existing one,
|
||||||
|
// or start a new one.
|
||||||
|
// give arguments to onCreateLoader()
|
||||||
|
getLoaderManager().initLoader(LOADER_ID_KEYBASE, null, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -157,16 +174,18 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
// Select checkbox!
|
// Select checkbox!
|
||||||
// Update underlying data and notify adapter of change. The adapter will
|
// Update underlying data and notify adapter of change. The adapter will
|
||||||
// update the view automatically.
|
// update the view automatically.
|
||||||
|
|
||||||
ImportKeysListEntry entry = mAdapter.getItem(position);
|
ImportKeysListEntry entry = mAdapter.getItem(position);
|
||||||
entry.setSelected(!entry.isSelected());
|
entry.setSelected(!entry.isSelected());
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadNew(byte[] keyBytes, Uri dataUri, String serverQuery, String keyServer) {
|
public void loadNew(byte[] keyBytes, Uri dataUri, String serverQuery, String keyServer, String keybaseQuery) {
|
||||||
mKeyBytes = keyBytes;
|
mKeyBytes = keyBytes;
|
||||||
mDataUri = dataUri;
|
mDataUri = dataUri;
|
||||||
mServerQuery = serverQuery;
|
mServerQuery = serverQuery;
|
||||||
mKeyServer = keyServer;
|
mKeyServer = keyServer;
|
||||||
|
mKeybaseQuery = keybaseQuery;
|
||||||
|
|
||||||
if (mKeyBytes != null || mDataUri != null) {
|
if (mKeyBytes != null || mDataUri != null) {
|
||||||
// Start out with a progress indicator.
|
// Start out with a progress indicator.
|
||||||
@ -181,11 +200,18 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
|
|
||||||
getLoaderManager().restartLoader(LOADER_ID_SERVER_QUERY, null, this);
|
getLoaderManager().restartLoader(LOADER_ID_SERVER_QUERY, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mKeybaseQuery != null) {
|
||||||
|
// Start out with a progress indicator.
|
||||||
|
setListShown(false);
|
||||||
|
|
||||||
|
getLoaderManager().restartLoader(LOADER_ID_KEYBASE, null, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>>
|
public Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>>
|
||||||
onCreateLoader(int id, Bundle args) {
|
onCreateLoader(int id, Bundle args) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case LOADER_ID_BYTES: {
|
case LOADER_ID_BYTES: {
|
||||||
InputData inputData = getInputData(mKeyBytes, mDataUri);
|
InputData inputData = getInputData(mKeyBytes, mDataUri);
|
||||||
@ -194,6 +220,9 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
case LOADER_ID_SERVER_QUERY: {
|
case LOADER_ID_SERVER_QUERY: {
|
||||||
return new ImportKeysListServerLoader(getActivity(), mServerQuery, mKeyServer);
|
return new ImportKeysListServerLoader(getActivity(), mServerQuery, mKeyServer);
|
||||||
}
|
}
|
||||||
|
case LOADER_ID_KEYBASE: {
|
||||||
|
return new ImportKeysListKeybaseLoader(getActivity(), mKeybaseQuery);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
@ -248,7 +277,7 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
if (error == null) {
|
if (error == null) {
|
||||||
AppMsg.makeText(
|
AppMsg.makeText(
|
||||||
getActivity(), getResources().getQuantityString(R.plurals.keys_found,
|
getActivity(), getResources().getQuantityString(R.plurals.keys_found,
|
||||||
mAdapter.getCount(), mAdapter.getCount()),
|
mAdapter.getCount(), mAdapter.getCount()),
|
||||||
AppMsg.STYLE_INFO
|
AppMsg.STYLE_INFO
|
||||||
).show();
|
).show();
|
||||||
} else if (error instanceof KeyServer.InsufficientQuery) {
|
} else if (error instanceof KeyServer.InsufficientQuery) {
|
||||||
@ -263,6 +292,19 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case LOADER_ID_KEYBASE:
|
||||||
|
|
||||||
|
if (error == null) {
|
||||||
|
AppMsg.makeText(
|
||||||
|
getActivity(), getResources().getQuantityString(R.plurals.keys_found,
|
||||||
|
mAdapter.getCount(), mAdapter.getCount()),
|
||||||
|
AppMsg.STYLE_INFO
|
||||||
|
).show();
|
||||||
|
} else if (error instanceof KeyServer.QueryException) {
|
||||||
|
AppMsg.makeText(getActivity(), R.string.error_keyserver_query,
|
||||||
|
AppMsg.STYLE_ALERT).show();
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -279,6 +321,10 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
// Clear the data in the adapter.
|
// Clear the data in the adapter.
|
||||||
mAdapter.clear();
|
mAdapter.clear();
|
||||||
break;
|
break;
|
||||||
|
case LOADER_ID_KEYBASE:
|
||||||
|
// Clear the data in the adapter.
|
||||||
|
mAdapter.clear();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ public class ImportKeysQrCodeFragment extends Fragment {
|
|||||||
|
|
||||||
// is this a full key encoded as qr code?
|
// is this a full key encoded as qr code?
|
||||||
if (scannedContent.startsWith("-----BEGIN PGP")) {
|
if (scannedContent.startsWith("-----BEGIN PGP")) {
|
||||||
mImportActivity.loadCallback(scannedContent.getBytes(), null, null, null);
|
mImportActivity.loadCallback(scannedContent.getBytes(), null, null, null, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ public class ImportKeysQrCodeFragment extends Fragment {
|
|||||||
for (String in : mScannedContent) {
|
for (String in : mScannedContent) {
|
||||||
result += in;
|
result += in;
|
||||||
}
|
}
|
||||||
mImportActivity.loadCallback(result.getBytes(), null, null, null);
|
mImportActivity.loadCallback(result.getBytes(), null, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ public class ImportKeysServerFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void search(String query, String keyServer) {
|
private void search(String query, String keyServer) {
|
||||||
mImportActivity.loadCallback(null, null, query, keyServer);
|
mImportActivity.loadCallback(null, null, query, keyServer, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
* Constructor for later querying from keyserver
|
* Constructor for later querying from keyserver
|
||||||
*/
|
*/
|
||||||
public ImportKeysListEntry() {
|
public ImportKeysListEntry() {
|
||||||
// keys from keyserver are always public keys
|
// keys from keyserver are always public keys; from keybase too
|
||||||
secretKey = false;
|
secretKey = false;
|
||||||
// do not select by default
|
// do not select by default
|
||||||
mSelected = false;
|
mSelected = false;
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.ui.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.util.KeyServer;
|
||||||
|
import org.sufficientlysecure.keychain.util.KeybaseKeyServer;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class ImportKeysListKeybaseLoader
|
||||||
|
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
|
||||||
|
Context mContext;
|
||||||
|
|
||||||
|
String mKeybaseQuery;
|
||||||
|
|
||||||
|
private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<ImportKeysListEntry>();
|
||||||
|
private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
|
||||||
|
|
||||||
|
public ImportKeysListKeybaseLoader(Context context, String keybaseQuery) {
|
||||||
|
super(context);
|
||||||
|
mContext = context;
|
||||||
|
mKeybaseQuery = keybaseQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
|
||||||
|
|
||||||
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
|
||||||
|
|
||||||
|
if (mKeybaseQuery == null) {
|
||||||
|
Log.e(Constants.TAG, "mKeybaseQery is null!");
|
||||||
|
return mEntryListWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
queryServer(mKeybaseQuery);
|
||||||
|
|
||||||
|
return mEntryListWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
|
super.onReset();
|
||||||
|
|
||||||
|
// Ensure the loader is stopped
|
||||||
|
onStopLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading() {
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopLoading() {
|
||||||
|
cancelLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
|
||||||
|
super.deliverResult(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query keybase
|
||||||
|
*/
|
||||||
|
private void queryServer(String query) {
|
||||||
|
|
||||||
|
KeybaseKeyServer server = new KeybaseKeyServer();
|
||||||
|
try {
|
||||||
|
ArrayList<ImportKeysListEntry> searchResult = server.search(query);
|
||||||
|
|
||||||
|
mEntryList.clear();
|
||||||
|
|
||||||
|
mEntryList.addAll(searchResult);
|
||||||
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
|
||||||
|
} catch (KeyServer.InsufficientQuery e) {
|
||||||
|
Log.e(Constants.TAG, "InsufficientQuery", e);
|
||||||
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
||||||
|
} catch (KeyServer.QueryException e) {
|
||||||
|
Log.e(Constants.TAG, "QueryException", e);
|
||||||
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
||||||
|
} catch (KeyServer.TooManyResponses e) {
|
||||||
|
Log.e(Constants.TAG, "TooManyResponses", e);
|
||||||
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package org.sufficientlysecure.keychain.util;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal hierarchy selector
|
||||||
|
*/
|
||||||
|
public class JWalk {
|
||||||
|
|
||||||
|
public static int getInt(JSONObject json, String... path) throws JSONException {
|
||||||
|
json = walk(json, path);
|
||||||
|
return json.getInt(path[path.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getLong(JSONObject json, String... path) throws JSONException {
|
||||||
|
json = walk(json, path);
|
||||||
|
return json.getLong(path[path.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getString(JSONObject json, String... path) throws JSONException {
|
||||||
|
json = walk(json, path);
|
||||||
|
return json.getString(path[path.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONArray getArray(JSONObject json, String... path) throws JSONException {
|
||||||
|
json = walk(json, path);
|
||||||
|
return json.getJSONArray(path[path.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONObject optObject(JSONObject json, String... path) throws JSONException {
|
||||||
|
json = walk(json, path);
|
||||||
|
return json.optJSONObject(path[path.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JSONObject walk(JSONObject json, String... path) throws JSONException {
|
||||||
|
int len = path.length - 1;
|
||||||
|
int pathIndex = 0;
|
||||||
|
try {
|
||||||
|
while (pathIndex < len) {
|
||||||
|
json = json.getJSONObject(path[pathIndex]);
|
||||||
|
pathIndex++;
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
// try to give ’em a nice-looking error
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
sb.append(path[i]).append('.');
|
||||||
|
}
|
||||||
|
sb.append(path[len]);
|
||||||
|
throw new JSONException("JWalk error at step " + pathIndex + " of " + sb);
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2011-2014 Thialfihar <thi@thialfihar.org>
|
||||||
|
* Copyright (C) 2011 Senecaso
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.util;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
public class KeybaseKeyServer extends KeyServer {
|
||||||
|
|
||||||
|
private WeakHashMap<String, String> mKeyCache = new WeakHashMap<String, String>();
|
||||||
|
|
||||||
|
private static String readAll(InputStream in, String encoding) throws IOException {
|
||||||
|
ByteArrayOutputStream raw = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
byte buffer[] = new byte[1 << 16];
|
||||||
|
int n = 0;
|
||||||
|
while ((n = in.read(buffer)) != -1) {
|
||||||
|
raw.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoding == null) {
|
||||||
|
encoding = "utf8";
|
||||||
|
}
|
||||||
|
return raw.toString(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArrayList<ImportKeysListEntry> search(String query) throws QueryException, TooManyResponses,
|
||||||
|
InsufficientQuery {
|
||||||
|
ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>();
|
||||||
|
|
||||||
|
JSONObject fromQuery = getFromKeybase("_/api/1.0/user/autocomplete.json?q=", query);
|
||||||
|
try {
|
||||||
|
|
||||||
|
JSONArray matches = JWalk.getArray(fromQuery, "completions");
|
||||||
|
for (int i = 0; i < matches.length(); i++) {
|
||||||
|
JSONObject match = matches.getJSONObject(i);
|
||||||
|
|
||||||
|
// only list them if they have a key
|
||||||
|
if (JWalk.optObject(match, "components", "key_fingerprint") != null) {
|
||||||
|
results.add(makeEntry(match));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new QueryException("Unexpected structure in keybase search result: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject getUser(String keybaseID) throws QueryException {
|
||||||
|
try {
|
||||||
|
return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseID);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String detail = "";
|
||||||
|
if (keybaseID != null) {
|
||||||
|
detail = ". Query was for user '" + keybaseID + "'";
|
||||||
|
}
|
||||||
|
throw new QueryException(e.getMessage() + detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImportKeysListEntry makeEntry(JSONObject match) throws QueryException, JSONException {
|
||||||
|
|
||||||
|
String keybaseID = JWalk.getString(match, "components", "username", "val");
|
||||||
|
String key_fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val");
|
||||||
|
key_fingerprint = key_fingerprint.replace(" ", "").toUpperCase();
|
||||||
|
match = getUser(keybaseID);
|
||||||
|
|
||||||
|
final ImportKeysListEntry entry = new ImportKeysListEntry();
|
||||||
|
|
||||||
|
entry.setBitStrength(4096);
|
||||||
|
entry.setAlgorithm("RSA");
|
||||||
|
entry.setKeyIdHex("0x" + key_fingerprint);
|
||||||
|
|
||||||
|
final long creationDate = JWalk.getLong(match, "them", "public_keys", "primary", "ctime");
|
||||||
|
final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||||
|
tmpGreg.setTimeInMillis(creationDate * 1000);
|
||||||
|
entry.setDate(tmpGreg.getTime());
|
||||||
|
entry.setRevoked(false);
|
||||||
|
mKeyCache.put(keybaseID, JWalk.getString(match,"them", "public_keys", "primary", "bundle"));
|
||||||
|
String name = JWalk.getString(match, "them", "profile", "full_name");
|
||||||
|
ArrayList<String> userIds = new ArrayList<String>();
|
||||||
|
userIds.add(name);
|
||||||
|
userIds.add("keybase.io/" + keybaseID); // TODO: Maybe should be keybaseID@keybase.io ?
|
||||||
|
entry.setUserIds(userIds);
|
||||||
|
entry.setPrimaryUserId(name);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject getFromKeybase(String path, String query) throws QueryException {
|
||||||
|
try {
|
||||||
|
String url = "https://keybase.io/" + path + URLEncoder.encode(query, "utf8");
|
||||||
|
Log.d(Constants.TAG, "keybase query: " + url);
|
||||||
|
|
||||||
|
URL realUrl = new URL(url);
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
|
||||||
|
conn.setConnectTimeout(5000); // TODO: Reasonable values for keybase
|
||||||
|
conn.setReadTimeout(25000);
|
||||||
|
conn.connect();
|
||||||
|
int response = conn.getResponseCode();
|
||||||
|
if (response >= 200 && response < 300) {
|
||||||
|
String text = readAll(conn.getInputStream(), conn.getContentEncoding());
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject(text);
|
||||||
|
if (JWalk.getInt(json, "status", "code") != 0) {
|
||||||
|
throw new QueryException("Keybase autocomplete search failed");
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new QueryException("Keybase.io query returned broken JSON");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String message = readAll(conn.getErrorStream(), conn.getContentEncoding());
|
||||||
|
throw new QueryException("Keybase.io query error (status=" + response +
|
||||||
|
"): " + message);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new QueryException("Keybase.io query error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get(String id) throws QueryException {
|
||||||
|
// id is like "keybase/username"
|
||||||
|
String keybaseID = id.substring(id.indexOf('/') + 1);
|
||||||
|
String key = mKeyCache.get(keybaseID);
|
||||||
|
if (key == null) {
|
||||||
|
try {
|
||||||
|
JSONObject user = getUser(keybaseID);
|
||||||
|
key = JWalk.getString(user, "them", "public_keys", "primary", "bundle");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new QueryException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(String armoredKey) throws AddKeyException {
|
||||||
|
throw new AddKeyException();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/import_keybase_query"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="top|left"
|
||||||
|
android:hint="@string/hint_keybase_search"
|
||||||
|
android:imeOptions="actionSearch"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:lines="1"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:minLines="1"
|
||||||
|
android:layout_gravity="center_vertical" />
|
||||||
|
|
||||||
|
<com.beardedhen.androidbootstrap.BootstrapButton
|
||||||
|
android:id="@+id/import_keybase_search"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
bootstrapbutton:bb_icon_left="fa-search"
|
||||||
|
bootstrapbutton:bb_roundedCorners="true"
|
||||||
|
bootstrapbutton:bb_size="default"
|
||||||
|
bootstrapbutton:bb_type="default" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<com.beardedhen.androidbootstrap.BootstrapButton
|
||||||
|
android:id="@+id/import_keybase_button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="70dp"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:text="@string/import_keybase_button"
|
||||||
|
bootstrapbutton:bb_size="default"
|
||||||
|
bootstrapbutton:bb_type="default" />
|
||||||
|
-->
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -54,6 +54,7 @@
|
|||||||
<item>@string/menu_import_from_qr_code</item>
|
<item>@string/menu_import_from_qr_code</item>
|
||||||
<item>@string/import_from_clipboard</item>
|
<item>@string/import_from_clipboard</item>
|
||||||
<item>@string/menu_import_from_nfc</item>
|
<item>@string/menu_import_from_nfc</item>
|
||||||
|
<item>@string/menu_import_from_keybase</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -75,6 +75,7 @@
|
|||||||
<string name="menu_create_key_expert">Create key (expert)</string>
|
<string name="menu_create_key_expert">Create key (expert)</string>
|
||||||
<string name="menu_search">Search</string>
|
<string name="menu_search">Search</string>
|
||||||
<string name="menu_import_from_key_server">Keyserver</string>
|
<string name="menu_import_from_key_server">Keyserver</string>
|
||||||
|
<string name="menu_import_from_keybase">Import from Keybase.io</string>
|
||||||
<string name="menu_key_server">Keyserver…</string>
|
<string name="menu_key_server">Keyserver…</string>
|
||||||
<string name="menu_update_key">Update from keyserver</string>
|
<string name="menu_update_key">Update from keyserver</string>
|
||||||
<string name="menu_export_key_to_server">Upload to key server</string>
|
<string name="menu_export_key_to_server">Upload to key server</string>
|
||||||
@ -348,6 +349,7 @@
|
|||||||
<string name="hint_public_keys">Search Public Keys</string>
|
<string name="hint_public_keys">Search Public Keys</string>
|
||||||
<string name="hint_secret_keys">Search Secret Keys</string>
|
<string name="hint_secret_keys">Search Secret Keys</string>
|
||||||
<string name="action_share_key_with">Share Key with…</string>
|
<string name="action_share_key_with">Share Key with…</string>
|
||||||
|
<string name="hint_keybase_search">Search Keybase.io</string>
|
||||||
|
|
||||||
<!-- key bit length selections -->
|
<!-- key bit length selections -->
|
||||||
<string name="key_size_512">512</string>
|
<string name="key_size_512">512</string>
|
||||||
@ -393,6 +395,7 @@
|
|||||||
<string name="import_nfc_text">To receive keys via NFC, the device needs to be unlocked.</string>
|
<string name="import_nfc_text">To receive keys via NFC, the device needs to be unlocked.</string>
|
||||||
<string name="import_nfc_help_button">Help</string>
|
<string name="import_nfc_help_button">Help</string>
|
||||||
<string name="import_clipboard_button">Get key from clipboard</string>
|
<string name="import_clipboard_button">Get key from clipboard</string>
|
||||||
|
<string name="import_keybase_button">Get key from Keybase.io</string>
|
||||||
|
|
||||||
<!-- Intent labels -->
|
<!-- Intent labels -->
|
||||||
<string name="intent_decrypt_file">Decrypt File with OpenKeychain</string>
|
<string name="intent_decrypt_file">Decrypt File with OpenKeychain</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user