Merge remote-tracking branch 'origin/master'

Conflicts:
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
	OpenKeychain/src/main/res/values-de/strings.xml
This commit is contained in:
Vincent Breitmoser
2014-09-15 14:00:17 +02:00
94 changed files with 1896 additions and 938 deletions

View File

@@ -81,6 +81,8 @@ public final class Constants {
public static final String CACHED_CONSOLIDATE = "cachedConsolidate";
public static final String CACHED_CONSOLIDATE_SECRETS = "cachedConsolidateSecrets";
public static final String CACHED_CONSOLIDATE_PUBLICS = "cachedConsolidatePublics";
public static final String SEARCH_KEYSERVER = "search_keyserver_pref";
public static final String SEARCH_KEYBASE = "search_keybase_pref";
public static final String USE_DEFAULT_YUBIKEY_PIN = "useDefaultYubikeyPin";
}

View File

@@ -61,9 +61,9 @@ public class EmailKeyHelper {
if (keys.isEmpty()) {
// Most users don't have the SRV record, so ask a default server as well
String[] servers = Preferences.getPreferences(context).getKeyServers();
if (servers != null && servers.length != 0) {
HkpKeyserver hkp = new HkpKeyserver(servers[0]);
String server = Preferences.getPreferences(context).getPreferredKeyserver();
if (server != null) {
HkpKeyserver hkp = new HkpKeyserver(server);
keys.addAll(getEmailKeys(mail, hkp));
}
}

View File

@@ -63,7 +63,7 @@ public class KeyUpdateHelper {
ImportKeysListEntry key = new ImportKeysListEntry();
key.setFingerprintHex(fprint);
key.setBitStrength(1337);
key.setOrigin(servers[0]);
key.addOrigin(servers[0]);
keys.add(key);
}

View File

@@ -220,6 +220,9 @@ public class Preferences {
}
return servers.toArray(chunks);
}
public String getPreferredKeyserver() {
return getKeyServers()[0];
}
public void setKeyServers(String[] value) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
@@ -248,6 +251,35 @@ public class Preferences {
return mSharedPreferences.getBoolean(Constants.Pref.WRITE_VERSION_HEADER, false);
}
public void setSearchKeyserver(boolean searchKeyserver) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Pref.SEARCH_KEYSERVER, searchKeyserver);
editor.commit();
}
public void setSearchKeybase(boolean searchKeybase) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Pref.SEARCH_KEYBASE, searchKeybase);
editor.commit();
}
public CloudSearchPrefs getCloudSearchPrefs() {
return new CloudSearchPrefs(mSharedPreferences.getBoolean(Pref.SEARCH_KEYSERVER, true),
mSharedPreferences.getBoolean(Pref.SEARCH_KEYBASE, true),
getPreferredKeyserver());
}
public static class CloudSearchPrefs {
public final boolean searchKeyserver;
public final boolean searchKeybase;
public final String keyserver;
public CloudSearchPrefs(boolean searchKeyserver, boolean searchKeybase, String keyserver) {
this.searchKeyserver = searchKeyserver;
this.searchKeybase = searchKeybase;
this.keyserver = keyserver;
}
}
public void updatePreferences() {
if (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0) !=
Constants.Defaults.PREF_VERSION) {

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2014 Tim Bray <tbray@textuality.com>
*
* 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.keyimport;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.Vector;
/**
* Search two or more types of server for online keys.
*/
public class CloudSearch {
private final static long SECONDS = 1000;
public static ArrayList<ImportKeysListEntry> search(final String query, Preferences.CloudSearchPrefs cloudPrefs)
throws Keyserver.CloudSearchFailureException {
final ArrayList<Keyserver> servers = new ArrayList<Keyserver>();
// it's a Vector for sync, multiple threads might report problems
final Vector<Keyserver.CloudSearchFailureException> problems = new Vector<Keyserver.CloudSearchFailureException>();
if (cloudPrefs.searchKeyserver) {
servers.add(new HkpKeyserver(cloudPrefs.keyserver));
}
if (cloudPrefs.searchKeybase) {
servers.add(new KeybaseKeyserver());
}
final ImportKeysList results = new ImportKeysList(servers.size());
for (final Keyserver keyserver : servers) {
Runnable r = new Runnable() {
@Override
public void run() {
try {
results.addAll(keyserver.search(query));
} catch (Keyserver.CloudSearchFailureException e) {
problems.add(e);
}
results.finishedAdding(); // notifies if all searchers done
}
};
new Thread(r).start();
}
// wait for either all the searches to come back, or 10 seconds
synchronized(results) {
try {
results.wait(10 * SECONDS);
} catch (InterruptedException e) {
}
}
if (results.outstandingSuppliers() > 0) {
String message = "Launched " + servers.size() + " cloud searchers, but" +
results.outstandingSuppliers() + "failed to complete.";
problems.add(new Keyserver.QueryFailedException(message));
}
if (!problems.isEmpty()) {
for (Keyserver.CloudSearchFailureException e : problems) {
Log.d(Constants.TAG, "Cloud search exception: " + e.getLocalizedMessage());
}
// only throw exception if we didnt get any results
if (results.isEmpty()) {
throw problems.get(0);
}
}
return results;
}
}

View File

@@ -278,7 +278,7 @@ public class HkpKeyserver extends Keyserver {
while (matcher.find()) {
final ImportKeysListEntry entry = new ImportKeysListEntry();
entry.setQuery(query);
entry.setOrigin(getUrlPrefix() + mHost + ":" + mPort);
entry.addOrigin(getUrlPrefix() + mHost + ":" + mPort);
int bitSize = Integer.parseInt(matcher.group(3));
entry.setBitStrength(bitSize);

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2014 Tim Bray <tbray@textuality.com>
*
* 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.keyimport;
import java.util.ArrayList;
import java.util.Collection;
/**
* Just an ArrayList, only with a synchronized dupe-merging add/addAll, and a sign-off method
*/
public class ImportKeysList extends ArrayList<ImportKeysListEntry> {
private int mSupplierCount;
public ImportKeysList(int supplierCount) {
mSupplierCount = supplierCount;
}
@Override
public boolean add(ImportKeysListEntry toAdd) {
addOrMerge(toAdd);
return true; // thats what the ArrayList#add contract says
}
@Override
public boolean addAll(Collection<? extends ImportKeysListEntry> addThese) {
boolean modified = false;
for (ImportKeysListEntry toAdd : addThese) {
modified = addOrMerge(toAdd) || modified;
}
return modified;
}
// NOTE: side-effects
// NOTE: synchronized
private synchronized boolean addOrMerge(ImportKeysListEntry toAdd) {
for (ImportKeysListEntry existing : this) {
if (toAdd.hasSameKeyAs(existing)) {
return mergeDupes(toAdd, existing);
}
}
return super.add(toAdd);
}
// being a little anal about the ArrayList#addAll contract here
private boolean mergeDupes(ImportKeysListEntry incoming, ImportKeysListEntry existing) {
boolean modified = false;
if (incoming.isRevoked()) {
existing.setRevoked(true);
modified = true;
}
if (incoming.isExpired()) {
existing.setExpired(true);
modified = true;
}
for (String origin : incoming.getOrigins()) {
existing.addOrigin(origin);
}
ArrayList<String> incomingIDs = incoming.getUserIds();
ArrayList<String> existingIDs = existing.getUserIds();
for (String incomingID : incomingIDs) {
if (!existingIDs.contains(incomingID)) {
existingIDs.add(incomingID);
modified = true;
}
}
return modified;
}
// NOTE: synchronized
public synchronized void finishedAdding() {
mSupplierCount--;
if (mSupplierCount == 0) {
this.notify();
}
}
public int outstandingSuppliers() {
return mSupplierCount;
}
}

View File

@@ -46,7 +46,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
private String mPrimaryUserId;
private String mExtraData;
private String mQuery;
private String mOrigin;
private ArrayList<String> mOrigins;
private Integer mHashCode = null;
private boolean mSelected;
@@ -70,7 +70,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
dest.writeByte((byte) (mSecretKey ? 1 : 0));
dest.writeByte((byte) (mSelected ? 1 : 0));
dest.writeString(mExtraData);
dest.writeString(mOrigin);
dest.writeStringList(mOrigins);
}
public static final Creator<ImportKeysListEntry> CREATOR = new Creator<ImportKeysListEntry>() {
@@ -90,7 +90,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
vr.mSecretKey = source.readByte() == 1;
vr.mSelected = source.readByte() == 1;
vr.mExtraData = source.readString();
vr.mOrigin = source.readString();
vr.mOrigins = new ArrayList<String>();
source.readStringList(vr.mOrigins);
return vr;
}
@@ -107,6 +108,13 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
return super.hashCode();
}
public boolean hasSameKeyAs(Object o) {
if (mFingerprintHex == null || o == null || !(o instanceof ImportKeysListEntry)) {
return false;
}
return mFingerprintHex.equals(((ImportKeysListEntry) o).mFingerprintHex);
}
public String getKeyIdHex() {
return mKeyIdHex;
}
@@ -223,12 +231,12 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mQuery = query;
}
public String getOrigin() {
return mOrigin;
public ArrayList<String> getOrigins() {
return mOrigins;
}
public void setOrigin(String origin) {
mOrigin = origin;
public void addOrigin(String origin) {
mOrigins.add(origin);
}
/**
@@ -240,6 +248,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
// do not select by default
mSelected = false;
mUserIds = new ArrayList<String>();
mOrigins = new ArrayList<String>();
}
/**

View File

@@ -63,7 +63,9 @@ public class KeybaseKeyserver extends Keyserver {
private ImportKeysListEntry makeEntry(Match match) throws KeybaseException {
final ImportKeysListEntry entry = new ImportKeysListEntry();
entry.setQuery(mQuery);
entry.setOrigin(ORIGIN);
entry.addOrigin(ORIGIN);
entry.setRevoked(false); // keybase doesnt say anything about revoked keys
String username = match.getUsername();
String fullName = match.getFullName();

View File

@@ -24,7 +24,19 @@ import java.io.InputStream;
import java.util.List;
public abstract class Keyserver {
public static class QueryFailedException extends Exception {
public static class CloudSearchFailureException extends Exception {
private static final long serialVersionUID = 2703768928624654515L;
public CloudSearchFailureException(String message) {
super(message);
}
public CloudSearchFailureException() {
super();
}
}
public static class QueryFailedException extends CloudSearchFailureException {
private static final long serialVersionUID = 2703768928624654512L;
public QueryFailedException(String message) {
@@ -32,7 +44,7 @@ public abstract class Keyserver {
}
}
public static class QueryNeedsRepairException extends Exception {
public static class QueryNeedsRepairException extends CloudSearchFailureException {
private static final long serialVersionUID = 2693768928624654512L;
}

View File

@@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.service.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.service.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Utf8Util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -363,7 +364,7 @@ public class UncachedKeyRing {
ArrayList<byte[]> processedUserIds = new ArrayList<byte[]>();
for (byte[] rawUserId : new IterableIterator<byte[]>(masterKey.getRawUserIDs())) {
String userId = Strings.fromUTF8ByteArray(rawUserId);
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
// check for duplicate user ids
if (processedUserIds.contains(rawUserId)) {
@@ -437,7 +438,7 @@ public class UncachedKeyRing {
continue;
}
// warn user if the signature was made with bad encoding
if (!cert.verifySignature(masterKey, userId)) {
if (!Utf8Util.isValidUTF8(rawUserId)) {
log.add(LogType.MSG_KC_UID_WARN_ENCODING, indent);
}
} catch (PgpGeneralException e) {

View File

@@ -32,6 +32,7 @@ import org.spongycastle.util.Strings;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Utf8Util;
import java.util.ArrayList;
import java.util.Arrays;
@@ -185,7 +186,7 @@ public class UncachedPublicKey {
}
}
if (found != null) {
return Strings.fromUTF8ByteArray(found);
return Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(found);
} else {
return null;
}
@@ -204,8 +205,9 @@ public class UncachedPublicKey {
public ArrayList<String> getUnorderedUserIds() {
ArrayList<String> userIds = new ArrayList<String>();
for (String userId : new IterableIterator<String>(mPublicKey.getUserIDs())) {
userIds.add(userId);
for (byte[] rawUserId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) {
// use our decoding method
userIds.add(Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId));
}
return userIds;
}

View File

@@ -65,6 +65,7 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressFixedScaler;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import org.sufficientlysecure.keychain.util.Utf8Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -437,8 +438,7 @@ public class ProviderHelper {
List<UserIdItem> uids = new ArrayList<UserIdItem>();
for (byte[] rawUserId : new IterableIterator<byte[]>(
masterKey.getUnorderedRawUserIds().iterator())) {
String userId = Strings.fromUTF8ByteArray(rawUserId);
Log.d(Constants.TAG, "userId: "+userId);
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
UserIdItem item = new UserIdItem();
uids.add(item);

View File

@@ -651,27 +651,35 @@ public class KeychainIntentService extends IntentService implements Progressable
for (ImportKeysListEntry entry : entries) {
try {
Keyserver server;
if (entry.getOrigin() == null) {
server = new HkpKeyserver(keyServer);
} else if (KeybaseKeyserver.ORIGIN.equals(entry.getOrigin())) {
server = new KeybaseKeyserver();
} else {
server = new HkpKeyserver(entry.getOrigin());
ArrayList<String> origins = entry.getOrigins();
if (origins == null) {
origins = new ArrayList<String>();
}
// if available use complete fingerprint for get request
byte[] downloadedKeyBytes;
if (KeybaseKeyserver.ORIGIN.equals(entry.getOrigin())) {
downloadedKeyBytes = server.get(entry.getExtraData()).getBytes();
} else if (entry.getFingerprintHex() != null) {
downloadedKeyBytes = server.get("0x" + entry.getFingerprintHex()).getBytes();
} else {
downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes();
if (origins.isEmpty()) {
origins.add(keyServer);
}
for (String origin : origins) {
if (KeybaseKeyserver.ORIGIN.equals(origin)) {
server = new KeybaseKeyserver();
} else {
server = new HkpKeyserver(origin);
}
Log.d(Constants.TAG, "IMPORTING " + entry.getKeyIdHex() + " FROM: " + server);
// save key bytes in entry object for doing the
// actual import afterwards
keyRings.add(new ParcelableKeyRing(downloadedKeyBytes, entry.getFingerprintHex()));
// if available use complete fingerprint for get request
byte[] downloadedKeyBytes;
if (KeybaseKeyserver.ORIGIN.equals(origin)) {
downloadedKeyBytes = server.get(entry.getExtraData()).getBytes();
} else if (entry.getFingerprintHex() != null) {
downloadedKeyBytes = server.get("0x" + entry.getFingerprintHex()).getBytes();
} else {
downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes();
}
// save key bytes in entry object for doing the
// actual import afterwards
keyRings.add(new ParcelableKeyRing(downloadedKeyBytes, entry.getFingerprintHex()));
}
} catch (Exception e) {
sendErrorToHandler(e);
}

View File

@@ -204,7 +204,7 @@ public class CreateKeyFinalFragment extends Fragment {
Bundle data = new Bundle();
// upload to favorite keyserver
String keyserver = Preferences.getPreferences(getActivity()).getKeyServers()[0];
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);

View File

@@ -1,6 +1,5 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
* 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
@@ -19,148 +18,40 @@
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.view.PagerTabStrip;
import android.support.v4.view.ViewPager;
import android.view.View;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.util.Log;
import java.util.regex.Matcher;
public class DecryptActivity extends DrawerActivity {
/* Intents */
public static final String ACTION_DECRYPT = OpenKeychainIntents.DECRYPT;
/* EXTRA keys for input */
public static final String EXTRA_TEXT = OpenKeychainIntents.DECRYPT_EXTRA_TEXT;
ViewPager mViewPager;
PagerTabStrip mPagerTabStrip;
PagerTabStripAdapter mTabsAdapter;
Bundle mMessageFragmentBundle = new Bundle();
Bundle mFileFragmentBundle = new Bundle();
int mSwitchToTab = PAGER_TAB_MESSAGE;
private static final int PAGER_TAB_MESSAGE = 0;
private static final int PAGER_TAB_FILE = 1;
private void initView() {
mViewPager = (ViewPager) findViewById(R.id.decrypt_pager);
mPagerTabStrip = (PagerTabStrip) findViewById(R.id.decrypt_pager_tab_strip);
mTabsAdapter = new PagerTabStripAdapter(this);
mViewPager.setAdapter(mTabsAdapter);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.decrypt_activity);
initView();
activateDrawerNavigation(savedInstanceState);
// Handle intent actions, maybe changes the bundles
handleActions(getIntent());
View actionFile = findViewById(R.id.decrypt_files);
View actionFromClipboard = findViewById(R.id.decrypt_from_clipboard);
mTabsAdapter.addTab(DecryptMessageFragment.class,
mMessageFragmentBundle, getString(R.string.label_message));
mTabsAdapter.addTab(DecryptFileFragment.class,
mFileFragmentBundle, getString(R.string.label_file));
mViewPager.setCurrentItem(mSwitchToTab);
}
/**
* Handles all actions with this intent
*
* @param intent
*/
private void handleActions(Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
String type = intent.getType();
Uri uri = intent.getData();
if (extras == null) {
extras = new Bundle();
}
/*
* Android's Action
*/
if (Intent.ACTION_SEND.equals(action) && type != null) {
// When sending to Keychain Decrypt via share menu
if ("text/plain".equals(type)) {
// Plain text
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (sharedText != null) {
// handle like normal text decryption, override action and extras to later
// executeServiceMethod ACTION_DECRYPT in main actions
extras.putString(EXTRA_TEXT, sharedText);
action = ACTION_DECRYPT;
}
} else {
// Binary via content provider (could also be files)
// override uri to get stream from send
uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
action = ACTION_DECRYPT;
actionFile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent filesDecrypt = new Intent(DecryptActivity.this, DecryptFilesActivity.class);
filesDecrypt.setAction(DecryptFilesActivity.ACTION_DECRYPT_DATA_OPEN);
startActivity(filesDecrypt);
}
} else if (Intent.ACTION_VIEW.equals(action)) {
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
});
// override action
action = ACTION_DECRYPT;
mFileFragmentBundle.putBoolean(DecryptFileFragment.ARG_FROM_VIEW_INTENT, true);
}
String textData = extras.getString(EXTRA_TEXT);
/**
* Main Actions
*/
if (ACTION_DECRYPT.equals(action) && textData != null) {
Log.d(Constants.TAG, "textData not null, matching text ...");
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(textData);
if (matcher.matches()) {
Log.d(Constants.TAG, "PGP_MESSAGE matched");
textData = matcher.group(1);
// replace non breakable spaces
textData = textData.replaceAll("\\xa0", " ");
mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData);
mSwitchToTab = PAGER_TAB_MESSAGE;
} else {
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(textData);
if (matcher.matches()) {
Log.d(Constants.TAG, "PGP_CLEARTEXT_SIGNATURE matched");
textData = matcher.group(1);
// replace non breakable spaces
textData = textData.replaceAll("\\xa0", " ");
mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData);
mSwitchToTab = PAGER_TAB_MESSAGE;
} else {
Log.d(Constants.TAG, "Nothing matched!");
}
actionFromClipboard.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent clipboardDecrypt = new Intent(DecryptActivity.this, DecryptTextActivity.class);
clipboardDecrypt.setAction(DecryptTextActivity.ACTION_DECRYPT_FROM_CLIPBOARD);
startActivity(clipboardDecrypt);
}
} else if (ACTION_DECRYPT.equals(action) && uri != null) {
mFileFragmentBundle.putParcelable(DecryptFileFragment.ARG_URI, uri);
mSwitchToTab = PAGER_TAB_FILE;
} else if (ACTION_DECRYPT.equals(action)) {
Log.e(Constants.TAG,
"Include the extra 'text' or an Uri with setData() in your Intent!");
}
});
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
import org.sufficientlysecure.keychain.util.Log;
public class DecryptFilesActivity extends ActionBarActivity {
/* Intents */
public static final String ACTION_DECRYPT_DATA = OpenKeychainIntents.DECRYPT_DATA;
// intern
public static final String ACTION_DECRYPT_DATA_OPEN = Constants.INTENT_PREFIX + "DECRYPT_DATA_OPEN";
DecryptFilesFragment mFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.decrypt_files_activity);
// Handle intent actions
handleActions(savedInstanceState, getIntent());
}
/**
* Handles all actions with this intent
*
* @param intent
*/
private void handleActions(Bundle savedInstanceState, Intent intent) {
String action = intent.getAction();
String type = intent.getType();
Uri uri = intent.getData();
Bundle mFileFragmentBundle = new Bundle();
/*
* Android's Action
*/
if (Intent.ACTION_SEND.equals(action) && type != null) {
// When sending to Keychain Decrypt via share menu
// Binary via content provider (could also be files)
// override uri to get stream from send
uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
action = ACTION_DECRYPT_DATA;
} else if (Intent.ACTION_VIEW.equals(action)) {
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
// override action
action = ACTION_DECRYPT_DATA;
}
/**
* Main Actions
*/
if (ACTION_DECRYPT_DATA.equals(action) && uri != null) {
mFileFragmentBundle.putParcelable(DecryptFilesFragment.ARG_URI, uri);
loadFragment(savedInstanceState, uri, false);
} else if (ACTION_DECRYPT_DATA_OPEN.equals(action)) {
loadFragment(savedInstanceState, null, true);
} else if (ACTION_DECRYPT_DATA.equals(action)) {
Log.e(Constants.TAG,
"Include an Uri with setData() in your Intent!");
}
}
private void loadFragment(Bundle savedInstanceState, Uri uri, boolean openDialog) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create an instance of the fragment
mFragment = DecryptFilesFragment.newInstance(uri, openDialog);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
.replace(R.id.decrypt_files_fragment_container, mFragment)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
}
}

View File

@@ -46,9 +46,10 @@ import org.sufficientlysecure.keychain.util.Notify;
import java.io.File;
public class DecryptFileFragment extends DecryptFragment {
public class DecryptFilesFragment extends DecryptFragment {
public static final String ARG_URI = "uri";
public static final String ARG_FROM_VIEW_INTENT = "view_intent";
// public static final String ARG_FROM_VIEW_INTENT = "view_intent";
public static final String ARG_OPEN_DIRECTLY = "open_directly";
private static final int REQUEST_CODE_INPUT = 0x00007003;
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
@@ -62,6 +63,22 @@ public class DecryptFileFragment extends DecryptFragment {
private Uri mInputUri = null;
private Uri mOutputUri = null;
/**
* Creates new instance of this fragment
*/
public static DecryptFilesFragment newInstance(Uri uri, boolean openDirectly) {
DecryptFilesFragment frag = new DecryptFilesFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_URI, uri);
// args.putBoolean(ARG_FROM_VIEW_INTENT, fromViewIntent);
args.putBoolean(ARG_OPEN_DIRECTLY, openDirectly);
frag.setArguments(args);
return frag;
}
/**
* Inflate the layout for this fragment
*/
@@ -75,9 +92,9 @@ public class DecryptFileFragment extends DecryptFragment {
view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
FileHelper.openDocument(DecryptFileFragment.this, "*/*", REQUEST_CODE_INPUT);
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
} else {
FileHelper.openFile(DecryptFileFragment.this, mInputUri, "*/*",
FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*",
REQUEST_CODE_INPUT);
}
}
@@ -97,6 +114,15 @@ public class DecryptFileFragment extends DecryptFragment {
super.onActivityCreated(savedInstanceState);
setInputUri(getArguments().<Uri>getParcelable(ARG_URI));
if (getArguments().getBoolean(ARG_OPEN_DIRECTLY, false)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
} else {
FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*",
REQUEST_CODE_INPUT);
}
}
}
private void setInputUri(Uri inputUri) {

View File

@@ -0,0 +1,158 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* 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 android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Notify;
import java.util.regex.Matcher;
public class DecryptTextActivity extends ActionBarActivity {
/* Intents */
public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT;
public static final String EXTRA_TEXT = OpenKeychainIntents.DECRYPT_EXTRA_TEXT;
// intern
public static final String ACTION_DECRYPT_FROM_CLIPBOARD = Constants.INTENT_PREFIX + "DECRYPT_TEXT_FROM_CLIPBOARD";
DecryptTextFragment mFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.decrypt_text_activity);
// Handle intent actions
handleActions(savedInstanceState, getIntent());
}
/**
* Handles all actions with this intent
*
* @param intent
*/
private void handleActions(Bundle savedInstanceState, Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
String type = intent.getType();
if (extras == null) {
extras = new Bundle();
}
String textData = null;
/*
* Android's Action
*/
if (Intent.ACTION_SEND.equals(action) && type != null) {
// When sending to Keychain Decrypt via share menu
if ("text/plain".equals(type)) {
// Plain text
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (sharedText != null) {
// handle like normal text decryption, override action and extras to later
// executeServiceMethod ACTION_DECRYPT_TEXT in main actions
textData = sharedText;
}
}
}
/**
* Main Actions
*/
textData = extras.getString(EXTRA_TEXT);
if (ACTION_DECRYPT_TEXT.equals(action) && textData != null) {
Log.d(Constants.TAG, "textData not null, matching text ...");
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(textData);
if (matcher.matches()) {
Log.d(Constants.TAG, "PGP_MESSAGE matched");
textData = matcher.group(1);
// replace non breakable spaces
textData = textData.replaceAll("\\xa0", " ");
} else {
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(textData);
if (matcher.matches()) {
Log.d(Constants.TAG, "PGP_CLEARTEXT_SIGNATURE matched");
textData = matcher.group(1);
// replace non breakable spaces
textData = textData.replaceAll("\\xa0", " ");
} else {
Notify.showNotify(this, R.string.error_invalid_data, Notify.Style.ERROR);
Log.d(Constants.TAG, "Nothing matched!");
}
}
} else if (ACTION_DECRYPT_FROM_CLIPBOARD.equals(action)) {
CharSequence clipboardText = ClipboardReflection.getClipboardText(this);
// only decrypt if clipboard content is available and a pgp message or cleartext signature
if (clipboardText != null) {
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText);
if (!matcher.matches()) {
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText);
}
if (matcher.matches()) {
textData = matcher.group(1);
} else {
Notify.showNotify(this, R.string.error_invalid_data, Notify.Style.ERROR);
}
} else {
Notify.showNotify(this, R.string.error_invalid_data, Notify.Style.ERROR);
}
} else if (ACTION_DECRYPT_TEXT.equals(action)) {
Log.e(Constants.TAG,
"Include the extra 'text' in your Intent!");
}
loadFragment(savedInstanceState, textData);
}
private void loadFragment(Bundle savedInstanceState, String ciphertext) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create an instance of the fragment
mFragment = DecryptTextFragment.newInstance(ciphertext);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
.replace(R.id.decrypt_text_fragment_container, mFragment)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
}
}

View File

@@ -24,62 +24,70 @@ import android.os.Message;
import android.os.Messenger;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.service.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Notify;
import java.util.regex.Matcher;
public class DecryptMessageFragment extends DecryptFragment {
public class DecryptTextFragment extends DecryptFragment {
public static final String ARG_CIPHERTEXT = "ciphertext";
// view
private EditText mMessage;
private View mDecryptButton;
private View mDecryptFromCLipboardButton;
// model
// // view
private TextView mMessage;
// private View mDecryptButton;
// private View mDecryptFromCLipboardButton;
//
// // model
private String mCiphertext;
/**
* Creates new instance of this fragment
*/
public static DecryptTextFragment newInstance(String ciphertext) {
DecryptTextFragment frag = new DecryptTextFragment();
Bundle args = new Bundle();
args.putString(ARG_CIPHERTEXT, ciphertext);
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.decrypt_message_fragment, container, false);
View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false);
mMessage = (EditText) view.findViewById(R.id.message);
mDecryptButton = view.findViewById(R.id.action_decrypt);
mDecryptFromCLipboardButton = view.findViewById(R.id.action_decrypt_from_clipboard);
mDecryptButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
decryptClicked();
}
});
mDecryptFromCLipboardButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
decryptFromClipboardClicked();
}
});
mMessage = (TextView) view.findViewById(R.id.decrypt_text_plaintext);
// mDecryptButton = view.findViewById(R.id.action_decrypt);
// mDecryptFromCLipboardButton = view.findViewById(R.id.action_decrypt_from_clipboard);
// mDecryptButton.setOnClickListener(new OnClickListener() {
// @Override
// public void onClick(View v) {
// decryptClicked();
// }
// });
// mDecryptFromCLipboardButton.setOnClickListener(new OnClickListener() {
// @Override
// public void onClick(View v) {
// decryptFromClipboardClicked();
// }
// });
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String ciphertext = getArguments().getString(ARG_CIPHERTEXT);
if (ciphertext != null) {
@@ -88,31 +96,6 @@ public class DecryptMessageFragment extends DecryptFragment {
}
}
private void decryptClicked() {
mCiphertext = mMessage.getText().toString();
decryptStart(null);
}
private void decryptFromClipboardClicked() {
CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
// only decrypt if clipboard content is available and a pgp message or cleartext signature
if (clipboardText != null) {
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText);
if (!matcher.matches()) {
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText);
}
if (matcher.matches()) {
mCiphertext = matcher.group(1);
decryptStart(null);
} else {
Notify.showNotify(getActivity(), R.string.error_invalid_data, Notify.Style.ERROR);
}
} else {
Notify.showNotify(getActivity(), R.string.error_invalid_data, Notify.Style.ERROR);
}
}
@Override
protected void decryptStart(String passphrase) {
Log.d(Constants.TAG, "decryptStart");

View File

@@ -68,8 +68,6 @@ public class ImportKeysActivity extends ActionBarActivity {
+ "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN";
public static final String ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN = Constants.INTENT_PREFIX
+ "IMPORT_KEY_FROM_FILE_AND_RETURN";
public static final String ACTION_IMPORT_KEY_FROM_KEYBASE = Constants.INTENT_PREFIX
+ "IMPORT_KEY_FROM_KEYBASE";
// Actions for internal use only:
public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
@@ -101,12 +99,11 @@ public class ImportKeysActivity extends ActionBarActivity {
public static final int VIEW_PAGER_HEIGHT = 64; // dp
private static final int ALL_TABS = -1;
private static final int TAB_KEYSERVER = 0;
private static final int TAB_CLOUD = 0;
private static final int TAB_QR_CODE = 1;
private static final int TAB_FILE = 2;
private static final int TAB_KEYBASE = 3;
private int mSwitchToTab = TAB_KEYSERVER;
private int mSwitchToTab = TAB_CLOUD;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -193,8 +190,8 @@ public class ImportKeysActivity extends ActionBarActivity {
if (query != null && query.length() > 0) {
// display keyserver fragment with query
serverBundle = new Bundle();
serverBundle.putString(ImportKeysServerFragment.ARG_QUERY, query);
mSwitchToTab = TAB_KEYSERVER;
serverBundle.putString(ImportKeysCloudFragment.ARG_QUERY, query);
mSwitchToTab = TAB_CLOUD;
// action: search immediately
startListFragment(savedInstanceState, null, null, query);
@@ -214,11 +211,11 @@ public class ImportKeysActivity extends ActionBarActivity {
// display keyserver fragment with query
serverBundle = new Bundle();
serverBundle.putString(ImportKeysServerFragment.ARG_QUERY, query);
serverBundle.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true);
serverBundle.putString(ImportKeysCloudFragment.ARG_QUERY, query);
serverBundle.putBoolean(ImportKeysCloudFragment.ARG_DISABLE_QUERY_EDIT, true);
// display server tab only
showTabOnly = TAB_KEYSERVER;
mSwitchToTab = TAB_KEYSERVER;
showTabOnly = TAB_CLOUD;
mSwitchToTab = TAB_CLOUD;
// action: search immediately
startListFragment(savedInstanceState, null, null, query);
@@ -257,12 +254,6 @@ public class ImportKeysActivity extends ActionBarActivity {
// NOTE: this only displays the appropriate fragment, no actions are taken
mSwitchToTab = TAB_QR_CODE;
// 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
mSwitchToTab = TAB_KEYBASE;
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
} else {
@@ -299,19 +290,17 @@ public class ImportKeysActivity extends ActionBarActivity {
switch (showTabOnly) {
case ALL_TABS:
// show all tabs
mTabsAdapter.addTab(ImportKeysServerFragment.class,
serverBundle, getString(R.string.import_tab_keyserver));
// show default tabs
mTabsAdapter.addTab(ImportKeysCloudFragment.class,
serverBundle, getString(R.string.import_tab_cloud));
mTabsAdapter.addTab(ImportKeysQrCodeFragment.class,
null, getString(R.string.import_tab_qr_code));
mTabsAdapter.addTab(ImportKeysFileFragment.class,
null, getString(R.string.import_tab_direct));
mTabsAdapter.addTab(ImportKeysKeybaseFragment.class,
null, getString(R.string.import_tab_keybase));
break;
case TAB_KEYSERVER:
mTabsAdapter.addTab(ImportKeysServerFragment.class,
serverBundle, getString(R.string.import_tab_keyserver));
case TAB_CLOUD:
mTabsAdapter.addTab(ImportKeysCloudFragment.class,
serverBundle, getString(R.string.import_tab_cloud));
break;
case TAB_QR_CODE:
mTabsAdapter.addTab(ImportKeysQrCodeFragment.class,
@@ -321,10 +310,6 @@ public class ImportKeysActivity extends ActionBarActivity {
mTabsAdapter.addTab(ImportKeysFileFragment.class,
null, getString(R.string.import_tab_direct));
break;
case TAB_KEYBASE:
mTabsAdapter.addTab(ImportKeysKeybaseFragment.class,
null, getString(R.string.import_tab_keybase));
break;
}
// update layout after operations
@@ -380,18 +365,17 @@ public class ImportKeysActivity extends ActionBarActivity {
if (mViewPager.getAdapter() != null)
mViewPager.setAdapter(null);
mViewPager.setAdapter(mTabsAdapter);
mViewPager.setCurrentItem(TAB_KEYSERVER);
mViewPager.setCurrentItem(TAB_CLOUD);
ImportKeysServerFragment f = (ImportKeysServerFragment)
getActiveFragment(mViewPager, TAB_KEYSERVER);
ImportKeysCloudFragment f = (ImportKeysCloudFragment)
getActiveFragment(mViewPager, TAB_CLOUD);
// ask favorite keyserver
String keyserver = Preferences.getPreferences(ImportKeysActivity.this).getKeyServers()[0];
// search config
Preferences prefs = Preferences.getPreferences(ImportKeysActivity.this);
Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
// set fields of ImportKeysServerFragment
f.setQueryAndKeyserver(query, keyserver);
// search directly
loadCallback(new ImportKeysListFragment.KeyserverLoaderState(query, keyserver));
loadCallback(new ImportKeysListFragment.CloudLoaderState(query, cloudPrefs));
}
// http://stackoverflow.com/a/9293207
@@ -522,8 +506,8 @@ public class ImportKeysActivity extends ActionBarActivity {
Log.e(Constants.TAG, "Problem writing cache file", e);
Notify.showNotify(this, "Problem writing cache file!", Notify.Style.ERROR);
}
} else if (ls instanceof ImportKeysListFragment.KeyserverLoaderState) {
ImportKeysListFragment.KeyserverLoaderState sls = (ImportKeysListFragment.KeyserverLoaderState) ls;
} else if (ls instanceof ImportKeysListFragment.CloudLoaderState) {
ImportKeysListFragment.CloudLoaderState sls = (ImportKeysListFragment.CloudLoaderState) ls;
// Send all information needed to service to query keys in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
@@ -533,7 +517,7 @@ public class ImportKeysActivity extends ActionBarActivity {
// fill values for this action
Bundle data = new Bundle();
data.putString(KeychainIntentService.DOWNLOAD_KEY_SERVER, sls.keyserver);
data.putString(KeychainIntentService.DOWNLOAD_KEY_SERVER, sls.mCloudPrefs.keyserver);
// get selected key entries
ArrayList<ImportKeysListEntry> selectedEntries = mListFragment.getSelectedEntries();
@@ -550,31 +534,6 @@ public class ImportKeysActivity extends ActionBarActivity {
// start service with intent
startService(intent);
} else if (ls instanceof ImportKeysListFragment.KeybaseLoaderState) {
// 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.getSelectedEntries();
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 {
Notify.showNotify(this, R.string.error_nothing_import, Notify.Style.ERROR);
}

View File

@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.KeyEvent;
@@ -30,8 +31,6 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
@@ -42,9 +41,8 @@ import org.sufficientlysecure.keychain.util.Log;
import java.util.List;
public class ImportKeysServerFragment extends Fragment {
public class ImportKeysCloudFragment extends Fragment {
public static final String ARG_QUERY = "query";
public static final String ARG_KEYSERVER = "keyserver";
public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit";
private ImportKeysActivity mImportActivity;
@@ -52,19 +50,16 @@ public class ImportKeysServerFragment extends Fragment {
private View mSearchButton;
private AutoCompleteTextView mQueryEditText;
private View mConfigButton;
private View mConfigLayout;
private Spinner mServerSpinner;
private ArrayAdapter<String> mServerAdapter;
/**
* Creates new instance of this fragment
*/
public static ImportKeysServerFragment newInstance(String query, String keyserver) {
ImportKeysServerFragment frag = new ImportKeysServerFragment();
public static ImportKeysCloudFragment newInstance(String query, String keyserver, boolean doKeyserver, boolean doKeybase) {
ImportKeysCloudFragment frag = new ImportKeysCloudFragment();
Bundle args = new Bundle();
args.putString(ARG_QUERY, query);
args.putString(ARG_KEYSERVER, keyserver);
frag.setArguments(args);
@@ -76,26 +71,11 @@ public class ImportKeysServerFragment extends Fragment {
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.import_keys_server_fragment, container, false);
View view = inflater.inflate(R.layout.import_keys_cloud_fragment, container, false);
mSearchButton = view.findViewById(R.id.import_server_search);
mQueryEditText = (AutoCompleteTextView) view.findViewById(R.id.import_server_query);
mConfigButton = view.findViewById(R.id.import_server_config_button);
mConfigLayout = view.findViewById(R.id.import_server_config);
mServerSpinner = (Spinner) view.findViewById(R.id.import_server_spinner);
// add keyservers to spinner
mServerAdapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_spinner_item, Preferences.getPreferences(getActivity())
.getKeyServers()
);
mServerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mServerSpinner.setAdapter(mServerAdapter);
if (mServerAdapter.getCount() > 0) {
mServerSpinner.setSelection(0);
} else {
mSearchButton.setEnabled(false);
}
mSearchButton = view.findViewById(R.id.cloud_import_server_search);
mQueryEditText = (AutoCompleteTextView) view.findViewById(R.id.cloud_import_server_query);
mConfigButton = view.findViewById(R.id.cloud_import_server_config_button);
List<String> namesAndEmails = ContactHelper.getContactNames(getActivity());
namesAndEmails.addAll(ContactHelper.getContactMails(getActivity()));
@@ -110,9 +90,7 @@ public class ImportKeysServerFragment extends Fragment {
mSearchButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String query = mQueryEditText.getText().toString();
String keyServer = (String) mServerSpinner.getSelectedItem();
search(query, keyServer);
search(mQueryEditText.getText().toString());
// close keyboard after pressing search
InputMethodManager imm =
@@ -125,9 +103,7 @@ public class ImportKeysServerFragment extends Fragment {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
String query = mQueryEditText.getText().toString();
String keyServer = (String) mServerSpinner.getSelectedItem();
search(query, keyServer);
search(mQueryEditText.getText().toString());
// Don't return true to let the keyboard close itself after pressing search
return false;
@@ -139,11 +115,11 @@ public class ImportKeysServerFragment extends Fragment {
mConfigButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mImportActivity.getViewPagerHeight() > ImportKeysActivity.VIEW_PAGER_HEIGHT) {
mImportActivity.resizeViewPager(ImportKeysActivity.VIEW_PAGER_HEIGHT);
} else {
mImportActivity.resizeViewPager(ImportKeysActivity.VIEW_PAGER_HEIGHT + 41);
}
Intent i = new Intent(mImportActivity, PreferencesActivity.class);
// GRR, for some reason I cant set the Action or I get an incomprehensible
// exception about modern two-pane layouts
// i.setAction(PreferencesActivity.ACTION_PREFS_CLOUD);
startActivity(i);
}
});
@@ -163,14 +139,6 @@ public class ImportKeysServerFragment extends Fragment {
Log.d(Constants.TAG, "query: " + query);
}
if (getArguments().containsKey(ARG_KEYSERVER)) {
String keyserver = getArguments().getString(ARG_KEYSERVER);
int keyserverPos = mServerAdapter.getPosition(keyserver);
mServerSpinner.setSelection(keyserverPos);
Log.d(Constants.TAG, "keyserver: " + keyserver);
}
if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) {
mQueryEditText.setEnabled(false);
}
@@ -184,14 +152,9 @@ public class ImportKeysServerFragment extends Fragment {
mImportActivity = (ImportKeysActivity) activity;
}
private void search(String query, String keyserver) {
mImportActivity.loadCallback(new ImportKeysListFragment.KeyserverLoaderState(query, keyserver));
}
public void setQueryAndKeyserver(String query, String keyserver) {
mQueryEditText.setText(query, TextView.BufferType.EDITABLE);
int keyServerPos = mServerAdapter.getPosition(keyserver);
mServerSpinner.setSelection(keyServerPos);
private void search(String query) {
Preferences prefs = Preferences.getPreferences(getActivity());
mImportActivity.loadCallback(new ImportKeysListFragment.CloudLoaderState(query, prefs.getCloudSearchPrefs()));
}
}

View File

@@ -1,122 +0,0 @@
/*
* 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;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
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 org.sufficientlysecure.keychain.R;
/**
* Import public keys from the Keybase.io directory. First cut: just raw search.
* TODO: make a pick list of the people youre following on keybase
*/
public class ImportKeysKeybaseFragment extends Fragment {
private ImportKeysActivity mImportActivity;
private View mSearchButton;
private EditText mQueryEditText;
public static final String ARG_QUERY = "query";
/**
* 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 = 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);
// set displayed values
if (getArguments() != null) {
if (getArguments().containsKey(ARG_QUERY)) {
String query = getArguments().getString(ARG_QUERY);
mQueryEditText.setText(query, TextView.BufferType.EDITABLE);
}
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mImportActivity = (ImportKeysActivity) activity;
}
private void search(String query) {
mImportActivity.loadCallback(new ImportKeysListFragment.KeybaseLoaderState(query));
}
}

View File

@@ -37,9 +37,8 @@ import org.sufficientlysecure.keychain.keyimport.Keyserver;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListKeybaseLoader;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListCloudLoader;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Notify;
@@ -63,8 +62,7 @@ public class ImportKeysListFragment extends ListFragment implements
private LoaderState mLoaderState;
private static final int LOADER_ID_BYTES = 0;
private static final int LOADER_ID_SERVER_QUERY = 1;
private static final int LOADER_ID_KEYBASE = 2;
private static final int LOADER_ID_CLOUD = 1;
private LongSparseArray<ParcelableKeyRing> mCachedKeyData;
@@ -118,21 +116,13 @@ public class ImportKeysListFragment extends ListFragment implements
}
}
static public class KeyserverLoaderState extends LoaderState {
String serverQuery;
String keyserver;
static public class CloudLoaderState extends LoaderState {
Preferences.CloudSearchPrefs mCloudPrefs;
String mServerQuery;
KeyserverLoaderState(String serverQuery, String keyserver) {
this.serverQuery = serverQuery;
this.keyserver = keyserver;
}
}
static public class KeybaseLoaderState extends LoaderState {
String keybaseQuery;
KeybaseLoaderState(String keybaseQuery) {
this.keybaseQuery = keybaseQuery;
CloudLoaderState(String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) {
mServerQuery = serverQuery;
mCloudPrefs = cloudPrefs;
}
}
@@ -161,9 +151,8 @@ public class ImportKeysListFragment extends ListFragment implements
} else if (query != null) {
// TODO: this is used when updating a key.
// Currently it simply uses keyserver nr 0
String keyserver = Preferences.getPreferences(getActivity())
.getKeyServers()[0];
mLoaderState = new KeyserverLoaderState(query, keyserver);
Preferences prefs = Preferences.getPreferences(getActivity());
mLoaderState = new CloudLoaderState(query, prefs.getCloudSearchPrefs());
}
getListView().setOnTouchListener(new OnTouchListener() {
@@ -202,11 +191,8 @@ public class ImportKeysListFragment extends ListFragment implements
if (getLoaderManager().getLoader(LOADER_ID_BYTES) != null) {
getLoaderManager().destroyLoader(LOADER_ID_BYTES);
}
if (getLoaderManager().getLoader(LOADER_ID_SERVER_QUERY) != null) {
getLoaderManager().destroyLoader(LOADER_ID_SERVER_QUERY);
}
if (getLoaderManager().getLoader(LOADER_ID_KEYBASE) != null) {
getLoaderManager().destroyLoader(LOADER_ID_KEYBASE);
if (getLoaderManager().getLoader(LOADER_ID_CLOUD) != null) {
getLoaderManager().destroyLoader(LOADER_ID_CLOUD);
}
if (getView() != null) {
setListShown(true);
@@ -219,16 +205,11 @@ public class ImportKeysListFragment extends ListFragment implements
setListShown(false);
getLoaderManager().restartLoader(LOADER_ID_BYTES, null, this);
} else if (mLoaderState instanceof KeyserverLoaderState) {
} else if (mLoaderState instanceof CloudLoaderState) {
// Start out with a progress indicator.
setListShown(false);
getLoaderManager().restartLoader(LOADER_ID_SERVER_QUERY, null, this);
} else if (mLoaderState instanceof KeybaseLoaderState) {
// Start out with a progress indicator.
setListShown(false);
getLoaderManager().restartLoader(LOADER_ID_KEYBASE, null, this);
getLoaderManager().restartLoader(LOADER_ID_CLOUD, null, this);
}
}
@@ -241,13 +222,9 @@ public class ImportKeysListFragment extends ListFragment implements
InputData inputData = getInputData(ls.keyBytes, ls.dataUri);
return new ImportKeysListLoader(mActivity, inputData);
}
case LOADER_ID_SERVER_QUERY: {
KeyserverLoaderState ls = (KeyserverLoaderState) mLoaderState;
return new ImportKeysListServerLoader(getActivity(), ls.serverQuery, ls.keyserver);
}
case LOADER_ID_KEYBASE: {
KeybaseLoaderState ls = (KeybaseLoaderState) mLoaderState;
return new ImportKeysListKeybaseLoader(getActivity(), ls.keybaseQuery);
case LOADER_ID_CLOUD: {
CloudLoaderState ls = (CloudLoaderState) mLoaderState;
return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs);
}
default:
@@ -301,8 +278,7 @@ public class ImportKeysListFragment extends ListFragment implements
}
break;
case LOADER_ID_SERVER_QUERY:
case LOADER_ID_KEYBASE:
case LOADER_ID_CLOUD:
if (error == null) {
// No error
@@ -333,11 +309,7 @@ public class ImportKeysListFragment extends ListFragment implements
// Clear the data in the adapter.
mAdapter.clear();
break;
case LOADER_ID_SERVER_QUERY:
// Clear the data in the adapter.
mAdapter.clear();
break;
case LOADER_ID_KEYBASE:
case LOADER_ID_CLOUD:
// Clear the data in the adapter.
mAdapter.clear();
break;

View File

@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.ui;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
@@ -40,7 +41,7 @@ import java.util.List;
@SuppressLint("NewApi")
public class PreferencesActivity extends PreferenceActivity {
public static final String ACTION_PREFS_GEN = "org.sufficientlysecure.keychain.ui.PREFS_GEN";
public static final String ACTION_PREFS_CLOUD = "org.sufficientlysecure.keychain.ui.PREFS_CLOUD";
public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV";
public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005;
@@ -55,16 +56,11 @@ public class PreferencesActivity extends PreferenceActivity {
String action = getIntent().getAction();
if (action != null && action.equals(ACTION_PREFS_GEN)) {
addPreferencesFromResource(R.xml.gen_preferences);
initializePassPassphraceCacheTtl(
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
if (action != null && action.equals(ACTION_PREFS_CLOUD)) {
addPreferencesFromResource(R.xml.cloud_search_prefs);
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
String servers[] = sPreferences.getKeyServers();
mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_keyservers,
servers.length, servers.length));
mKeyServerPreference.setSummary(keyserverSummary(this));
mKeyServerPreference
.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
@@ -76,10 +72,19 @@ public class PreferencesActivity extends PreferenceActivity {
return false;
}
});
initializeSearchKeyserver(
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
);
initializeSearchKeybase(
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
);
} else if (action != null && action.equals(ACTION_PREFS_ADV)) {
addPreferencesFromResource(R.xml.adv_preferences);
initializePassPassphraceCacheTtl(
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
initializeEncryptionAlgorithm(
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM));
@@ -136,8 +141,7 @@ public class PreferencesActivity extends PreferenceActivity {
String servers[] = data
.getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS);
sPreferences.setKeyServers(servers);
mKeyServerPreference.setSummary(getResources().getQuantityString(
R.plurals.n_keyservers, servers.length, servers.length));
mKeyServerPreference.setSummary(keyserverSummary(this));
break;
}
@@ -156,9 +160,9 @@ public class PreferencesActivity extends PreferenceActivity {
}
/**
* This fragment shows the general preferences in android 3.0+
* This fragment shows the Cloud Search preferences in android 3.0+
*/
public static class GeneralPrefsFragment extends PreferenceFragment {
public static class CloudSearchPrefsFragment extends PreferenceFragment {
private PreferenceScreen mKeyServerPreference = null;
@@ -167,15 +171,11 @@ public class PreferencesActivity extends PreferenceActivity {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.gen_preferences);
initializePassPassphraceCacheTtl(
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
addPreferencesFromResource(R.xml.cloud_search_prefs);
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
String servers[] = sPreferences.getKeyServers();
mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_keyservers,
servers.length, servers.length));
mKeyServerPreference.setSummary(keyserverSummary(getActivity()));
mKeyServerPreference
.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
@@ -187,6 +187,12 @@ public class PreferencesActivity extends PreferenceActivity {
return false;
}
});
initializeSearchKeyserver(
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
);
initializeSearchKeybase(
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
);
}
@Override
@@ -199,8 +205,7 @@ public class PreferencesActivity extends PreferenceActivity {
String servers[] = data
.getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS);
sPreferences.setKeyServers(servers);
mKeyServerPreference.setSummary(getResources().getQuantityString(
R.plurals.n_keyservers, servers.length, servers.length));
mKeyServerPreference.setSummary(keyserverSummary(getActivity()));
break;
}
@@ -224,6 +229,9 @@ public class PreferencesActivity extends PreferenceActivity {
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.adv_preferences);
initializePassPassphraceCacheTtl(
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
initializeEncryptionAlgorithm(
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM));
@@ -271,7 +279,7 @@ public class PreferencesActivity extends PreferenceActivity {
protected boolean isValidFragment(String fragmentName) {
return AdvancedPrefsFragment.class.getName().equals(fragmentName)
|| GeneralPrefsFragment.class.getName().equals(fragmentName)
|| CloudSearchPrefsFragment.class.getName().equals(fragmentName)
|| super.isValidFragment(fragmentName);
}
@@ -398,6 +406,39 @@ public class PreferencesActivity extends PreferenceActivity {
});
}
private static void initializeSearchKeyserver(final CheckBoxPreference mSearchKeyserver) {
Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs();
mSearchKeyserver.setChecked(prefs.searchKeyserver);
mSearchKeyserver.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
mSearchKeyserver.setChecked((Boolean) newValue);
sPreferences.setSearchKeyserver((Boolean) newValue);
return false;
}
});
}
private static void initializeSearchKeybase(final CheckBoxPreference mSearchKeybase) {
Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs();
mSearchKeybase.setChecked(prefs.searchKeybase);
mSearchKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
mSearchKeybase.setChecked((Boolean) newValue);
sPreferences.setSearchKeybase((Boolean) newValue);
return false;
}
});
}
public static String keyserverSummary(Context context) {
String[] servers = sPreferences.getKeyServers();
String serverSummary = context.getResources().getQuantityString(
R.plurals.n_keyservers, servers.length, servers.length);
return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences.getPreferredKeyserver();
}
private static void initializeUseDefaultYubikeyPin(final CheckBoxPreference mUseDefaultYubikeyPin) {
mUseDefaultYubikeyPin.setChecked(sPreferences.useDefaultYubikeyPin());
mUseDefaultYubikeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@@ -408,4 +449,5 @@ public class PreferencesActivity extends PreferenceActivity {
}
});
}
}

View File

@@ -43,6 +43,7 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O
private LayoutInflater mInflater;
private ViewGroup mEditors;
private View mAdd;
private View mRotate;
private TextView mTitle;
private TextView mSummary;
@@ -73,6 +74,7 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O
mTitle = (TextView) findViewById(R.id.title);
mSummary = (TextView) findViewById(R.id.summary);
mSummary.setText(getText(R.string.label_first_keyserver_is_used));
mTitle.setText(R.string.label_keyservers);
@@ -80,9 +82,29 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O
mAdd = findViewById(R.id.add);
mAdd.setOnClickListener(this);
mRotate = findViewById(R.id.rotate);
mRotate.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Vector<String> servers = serverList();
String first = servers.get(0);
if (first != null) {
servers.remove(0);
servers.add(first);
String[] dummy = {};
makeServerList(servers.toArray(dummy));
}
}
});
Intent intent = getIntent();
String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS);
makeServerList(servers);
}
private void makeServerList(String[] servers) {
if (servers != null) {
mEditors.removeAllViews();
for (String serv : servers) {
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(
R.layout.key_server_editor, mEditors, false);
@@ -114,6 +136,18 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O
finish();
}
private Vector<String> serverList() {
Vector<String> servers = new Vector<String>();
for (int i = 0; i < mEditors.getChildCount(); ++i) {
KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i);
String tmp = editor.getValue();
if (tmp.length() > 0) {
servers.add(tmp);
}
}
return servers;
}
private void okClicked() {
Intent data = new Intent();
Vector<String> servers = new Vector<String>();

View File

@@ -113,7 +113,7 @@ public class UploadKeyActivity extends ActionBarActivity {
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
Toast.makeText(UploadKeyActivity.this, R.string.key_send_success,
Toast.makeText(UploadKeyActivity.this, R.string.msg_crt_upload_success,
Toast.LENGTH_SHORT).show();
finish();
}

View File

@@ -21,28 +21,29 @@ import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.keyimport.CloudSearch;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.Keyserver;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
public class ImportKeysListServerLoader
public class ImportKeysListCloudLoader
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
Context mContext;
Preferences.CloudSearchPrefs mCloudPrefs;
String mServerQuery;
String mKeyServer;
private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<ImportKeysListEntry>();
private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
public ImportKeysListServerLoader(Context context, String serverQuery, String keyServer) {
public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) {
super(context);
mContext = context;
mServerQuery = serverQuery;
mKeyServer = keyServer;
mCloudPrefs = cloudPrefs;
}
@Override
@@ -56,9 +57,9 @@ public class ImportKeysListServerLoader
if (mServerQuery.startsWith("0x") && mServerQuery.length() == 42) {
Log.d(Constants.TAG, "This search is based on a unique fingerprint. Enforce a fingerprint check!");
queryServer(mServerQuery, mKeyServer, true);
queryServer(true);
} else {
queryServer(mServerQuery, mKeyServer, false);
queryServer(false);
}
return mEntryListWrapper;
@@ -90,15 +91,14 @@ public class ImportKeysListServerLoader
/**
* Query keyserver
*/
private void queryServer(String query, String keyServer, boolean enforceFingerprint) {
HkpKeyserver server = new HkpKeyserver(keyServer);
private void queryServer(boolean enforceFingerprint) {
try {
ArrayList<ImportKeysListEntry> searchResult = server.search(query);
ArrayList<ImportKeysListEntry> searchResult = CloudSearch.search(mServerQuery, mCloudPrefs);
mEntryList.clear();
// add result to data
if (enforceFingerprint) {
String fingerprint = query.substring(2);
String fingerprint = mServerQuery.substring(2);
Log.d(Constants.TAG, "fingerprint: " + fingerprint);
// query must return only one result!
if (searchResult.size() == 1) {
@@ -115,11 +115,8 @@ public class ImportKeysListServerLoader
mEntryList.addAll(searchResult);
}
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
} catch (Keyserver.QueryFailedException e) {
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
} catch (Keyserver.QueryNeedsRepairException e) {
} catch (Exception e) {
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
}
}
}

View File

@@ -1,103 +0,0 @@
/*
* 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.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
import org.sufficientlysecure.keychain.keyimport.Keyserver;
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.QueryFailedException e) {
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
} catch (Keyserver.QueryNeedsRepairException e) {
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.util;
import org.sufficientlysecure.keychain.Constants;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
public class Utf8Util {
public static boolean isValidUTF8(byte[] input) {
CharsetDecoder cs = Charset.forName("UTF-8").newDecoder();
try {
cs.decode(ByteBuffer.wrap(input));
return true;
} catch (CharacterCodingException e) {
return false;
}
}
public static String fromUTF8ByteArrayReplaceBadEncoding(byte[] input) {
final CharsetDecoder charsetDecoder = Charset.forName("UTF-8").newDecoder();
charsetDecoder.onMalformedInput(CodingErrorAction.REPLACE);
charsetDecoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
try {
return charsetDecoder.decode(ByteBuffer.wrap(input)).toString();
} catch (CharacterCodingException e) {
Log.e(Constants.TAG, "Decoding failed!", e);
return charsetDecoder.replacement();
}
}
}