aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java28
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java20
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java27
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java111
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java11
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java14
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java88
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java11
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java79
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java114
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java106
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java33
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java29
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java145
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java23
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java11
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java14
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java17
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java13
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java21
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/RevokeResult.java123
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java13
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java13
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java91
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java110
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java38
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java18
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java26
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java46
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java24
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java16
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/RevokeKeyringParcel.java66
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java53
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java76
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java84
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java174
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java26
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java89
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java33
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java143
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java58
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyBlankFragment.java)8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java)26
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java134
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java152
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java)2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java128
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java427
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java21
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java99
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java44
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java40
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java21
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java23
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java54
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java27
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java206
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java15
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java83
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java143
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java91
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java82
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RetryUploadDialogActivity.java88
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java303
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java194
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java367
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java330
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java33
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java30
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java24
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java46
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java44
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java31
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java245
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java104
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java172
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/QueueingCryptoOperationFragment.java93
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java)123
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java31
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java211
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java29
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java126
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java75
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java76
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java132
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java39
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java73
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java106
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java41
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java92
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java39
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java70
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java28
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java84
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java22
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java40
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java88
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java13
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java119
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java51
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java252
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java144
155 files changed, 6544 insertions, 2072 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
index d1b37aed2..3d58602ab 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
@@ -26,6 +26,8 @@ import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.BuildConfig;
import java.io.File;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
public final class Constants {
@@ -44,6 +46,8 @@ public final class Constants {
public static final String PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
public static final String TEMPSTORAGE_AUTHORITY = BuildConfig.APPLICATION_ID + ".tempstorage";
+ public static final String CLIPBOARD_LABEL = "Keychain";
+
// as defined in http://tools.ietf.org/html/rfc3156, section 7
public static final String NFC_MIME = "application/pgp-keys";
@@ -93,11 +97,33 @@ public final class Constants {
public static final String FILE_USE_COMPRESSION = "useFileCompression";
public static final String TEXT_USE_COMPRESSION = "useTextCompression";
public static final String USE_ARMOR = "useArmor";
+ // proxy settings
+ public static final String USE_NORMAL_PROXY = "useNormalProxy";
+ public static final String USE_TOR_PROXY = "useTorProxy";
+ public static final String PROXY_HOST = "proxyHost";
+ public static final String PROXY_PORT = "proxyPort";
+ public static final String PROXY_TYPE = "proxyType";
+ public static final String THEME = "theme";
+
+ public static final class Theme {
+ public static final String LIGHT = "light";
+ public static final String DARK = "dark";
+ public static final String DEFAULT = Constants.Pref.Theme.LIGHT;
+ }
+ }
+
+ /**
+ * information to connect to Orbot's localhost HTTP proxy
+ */
+ public static final class Orbot {
+ public static final String PROXY_HOST = "127.0.0.1";
+ public static final int PROXY_PORT = 8118;
+ public static final Proxy.Type PROXY_TYPE = Proxy.Type.HTTP;
}
public static final class Defaults {
public static final String KEY_SERVERS = "hkps://hkps.pool.sks-keyservers.net, hkps://pgp.mit.edu";
- public static final int PREF_VERSION = 4;
+ public static final int PREF_VERSION = 5;
}
public static final class key {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
index 627623f47..cd24394d7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
@@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
+import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PRNGFixes;
import org.sufficientlysecure.keychain.util.Preferences;
@@ -91,12 +92,12 @@ public class KeychainApplication extends Application {
}
brandGlowEffect(getApplicationContext(),
- getApplicationContext().getResources().getColor(R.color.primary));
+ FormattingUtils.getColorFromAttr(getApplicationContext(), R.attr.colorPrimary));
setupAccountAsNeeded(this);
// Update keyserver list as needed
- Preferences.getPreferences(this).updatePreferences();
+ Preferences.getPreferences(this).upgradePreferences();
TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer");
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java
index 403e654e4..abf16851d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java
@@ -17,28 +17,22 @@
package org.sufficientlysecure.keychain.compatibility;
+
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
+import android.support.annotation.Nullable;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;
-import java.lang.reflect.Method;
-
public class ClipboardReflection {
- private static final String clipboardLabel = "Keychain";
-
- public static void copyToClipboard(Context context, String text) {
- ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
-
- ClipData clip = ClipData.newPlainText(clipboardLabel, text);
- clipboard.setPrimaryClip(clip);
-
- }
-
- public static String getClipboardText(Context context) {
+ @Nullable
+ public static String getClipboardText(@Nullable Context context) {
+ if (context == null) {
+ return null;
+ }
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = clipboard.getPrimaryClip();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java
index 07799f466..e1d8c0da7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java
@@ -21,7 +21,7 @@ import android.os.Build;
import android.os.Handler;
/**
- * Bug on Android >= 4.2
+ * Bug on Android >= 4.2. Fixed in 4.2.2 ?
*
* http://code.google.com/p/android/issues/detail?id=41901
*
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java
index c0221fad3..d91dd28bc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java
@@ -20,6 +20,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
+import java.net.Proxy;
import java.util.ArrayList;
import java.util.Vector;
@@ -30,7 +31,8 @@ public class CloudSearch {
private final static long SECONDS = 1000;
- public static ArrayList<ImportKeysListEntry> search(final String query, Preferences.CloudSearchPrefs cloudPrefs)
+ public static ArrayList<ImportKeysListEntry> search(final String query, Preferences.CloudSearchPrefs cloudPrefs,
+ final Proxy proxy)
throws Keyserver.CloudSearchFailureException {
final ArrayList<Keyserver> servers = new ArrayList<>();
@@ -45,31 +47,42 @@ public class CloudSearch {
}
final ImportKeysList results = new ImportKeysList(servers.size());
+ ArrayList<Thread> searchThreads = new ArrayList<>();
for (final Keyserver keyserver : servers) {
Runnable r = new Runnable() {
@Override
public void run() {
try {
- results.addAll(keyserver.search(query));
+ results.addAll(keyserver.search(query, proxy));
} catch (Keyserver.CloudSearchFailureException e) {
problems.add(e);
}
results.finishedAdding(); // notifies if all searchers done
}
};
- new Thread(r).start();
+ Thread searchThread = new Thread(r);
+ searchThreads.add(searchThread);
+ searchThread.start();
}
- // wait for either all the searches to come back, or 10 seconds
- synchronized(results) {
+ // wait for either all the searches to come back, or 10 seconds. If using proxy, wait 30 seconds.
+ synchronized (results) {
try {
- results.wait(10 * SECONDS);
+ if (proxy != null) {
+ results.wait(30 * SECONDS);
+ } else {
+ results.wait(10 * SECONDS);
+ }
+ for (Thread thread : searchThreads) {
+ // kill threads that haven't returned yet
+ thread.interrupt();
+ }
} catch (InterruptedException e) {
}
}
if (results.outstandingSuppliers() > 0) {
- String message = "Launched " + servers.size() + " cloud searchers, but" +
+ String message = "Launched " + servers.size() + " cloud searchers, but " +
results.outstandingSuppliers() + "failed to complete.";
problems.add(new Keyserver.QueryFailedException(message));
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
index cb8a53e25..2cf6d8b34 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
@@ -18,18 +18,20 @@
package org.sufficientlysecure.keychain.keyimport;
+import com.squareup.okhttp.MediaType;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.RequestBody;
+import com.squareup.okhttp.Response;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.TlsHelper;
-import java.io.BufferedWriter;
import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
+import java.net.Proxy;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
@@ -39,6 +41,7 @@ import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -190,36 +193,52 @@ public class HkpKeyserver extends Keyserver {
return mSecure ? "https://" : "http://";
}
- private HttpURLConnection openConnection(URL url) throws IOException {
- HttpURLConnection conn = null;
+ /**
+ * returns a client with pinned certificate if necessary
+ *
+ * @param url
+ * @param proxy
+ * @return
+ */
+ public static OkHttpClient getClient(URL url, Proxy proxy) throws IOException {
+ OkHttpClient client = new OkHttpClient();
+
try {
- conn = (HttpURLConnection) TlsHelper.openConnection(url);
+ TlsHelper.pinCertificateIfNecessary(client, url);
} catch (TlsHelper.TlsHelperException e) {
Log.w(Constants.TAG, e);
}
- if (conn == null) {
- conn = (HttpURLConnection) url.openConnection();
+
+ if (proxy != null) {
+ client.setProxy(proxy);
+ client.setConnectTimeout(30000, TimeUnit.MILLISECONDS);
+ } else {
+ client.setProxy(Proxy.NO_PROXY);
+ client.setConnectTimeout(5000, TimeUnit.MILLISECONDS);
}
- conn.setConnectTimeout(5000);
- conn.setReadTimeout(25000);
- return conn;
+ client.setReadTimeout(45000, TimeUnit.MILLISECONDS);
+
+ return client;
}
- private String query(String request) throws QueryFailedException, HttpError {
+ private String query(String request, Proxy proxy) throws QueryFailedException, HttpError {
try {
URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request);
- Log.d(Constants.TAG, "hkp keyserver query: " + url);
- HttpURLConnection conn = openConnection(url);
- conn.connect();
- int response = conn.getResponseCode();
- if (response >= 200 && response < 300) {
- return readAll(conn.getInputStream(), conn.getContentEncoding());
+ Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy);
+ OkHttpClient client = getClient(url, proxy);
+ Response response = client.newCall(new Request.Builder().url(url).build()).execute();
+
+ String responseBody = response.body().string();// contains body both in case of success or failure
+
+ if (response.isSuccessful()) {
+ return responseBody;
} else {
- String data = readAll(conn.getErrorStream(), conn.getContentEncoding());
- throw new HttpError(response, data);
+ throw new HttpError(response.code(), responseBody);
}
} catch (IOException e) {
- throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!");
+ Log.e(Constants.TAG, "IOException at HkpKeyserver", e);
+ throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!" +
+ proxy == null?"":" Using proxy " + proxy);
}
}
@@ -232,7 +251,7 @@ public class HkpKeyserver extends Keyserver {
* @throws QueryNeedsRepairException
*/
@Override
- public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException,
+ public ArrayList<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException,
QueryNeedsRepairException {
ArrayList<ImportKeysListEntry> results = new ArrayList<>();
@@ -250,7 +269,7 @@ public class HkpKeyserver extends Keyserver {
String data;
try {
- data = query(request);
+ data = query(request, proxy);
} catch (HttpError e) {
if (e.getData() != null) {
Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.ENGLISH));
@@ -334,13 +353,14 @@ public class HkpKeyserver extends Keyserver {
}
@Override
- public String get(String keyIdHex) throws QueryFailedException {
+ public String get(String keyIdHex, Proxy proxy) throws QueryFailedException {
String request = "/pks/lookup?op=get&options=mr&search=" + keyIdHex;
- Log.d(Constants.TAG, "hkp keyserver get: " + request);
+ Log.d(Constants.TAG, "hkp keyserver get: " + request + " using Proxy: " + proxy);
String data;
try {
- data = query(request);
+ data = query(request, proxy);
} catch (HttpError httpError) {
+ Log.e(Constants.TAG, "Failed to get key at HkpKeyserver", httpError);
throw new QueryFailedException("not found");
}
Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data);
@@ -351,38 +371,38 @@ public class HkpKeyserver extends Keyserver {
}
@Override
- public void add(String armoredKey) throws AddKeyException {
+ public void add(String armoredKey, Proxy proxy) throws AddKeyException {
try {
- String request = "/pks/add";
+ String path = "/pks/add";
String params;
try {
params = "keytext=" + URLEncoder.encode(armoredKey, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AddKeyException();
}
- URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request);
+ URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + path);
Log.d(Constants.TAG, "hkp keyserver add: " + url.toString());
Log.d(Constants.TAG, "params: " + params);
- HttpURLConnection conn = openConnection(url);
- conn.setRequestMethod("POST");
- conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
- conn.setRequestProperty("Content-Length", Integer.toString(params.getBytes().length));
- conn.setDoInput(true);
- conn.setDoOutput(true);
+ RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), params);
+
+ Request request = new Request.Builder()
+ .url(url)
+ .addHeader("Content-Type", "application/x-www-form-urlencoded")
+ .addHeader("Content-Length", Integer.toString(params.getBytes().length))
+ .post(body)
+ .build();
- OutputStream os = conn.getOutputStream();
- BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
- writer.write(params);
- writer.flush();
- writer.close();
- os.close();
+ Response response = getClient(url, proxy).newCall(request).execute();
- conn.connect();
+ Log.d(Constants.TAG, "response code: " + response.code());
+ Log.d(Constants.TAG, "answer: " + response.body().string());
+
+ if (response.code() != 200) {
+ throw new AddKeyException();
+ }
- Log.d(Constants.TAG, "response code: " + conn.getResponseCode());
- Log.d(Constants.TAG, "answer: " + readAll(conn.getInputStream(), conn.getContentEncoding()));
} catch (IOException e) {
Log.e(Constants.TAG, "IOException", e);
throw new AddKeyException();
@@ -398,6 +418,7 @@ public class HkpKeyserver extends Keyserver {
* Tries to find a server responsible for a given domain
*
* @return A responsible Keyserver or null if not found.
+ * TODO: PHILIP Add proxy functionality
*/
public static HkpKeyserver resolve(String domain) {
try {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
index e310e9a3f..c2865410e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
@@ -26,6 +26,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
+import java.net.Proxy;
import java.util.ArrayList;
import java.util.List;
@@ -34,7 +35,7 @@ public class KeybaseKeyserver extends Keyserver {
private String mQuery;
@Override
- public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException,
+ public ArrayList<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException,
QueryNeedsRepairException {
ArrayList<ImportKeysListEntry> results = new ArrayList<>();
@@ -48,7 +49,7 @@ public class KeybaseKeyserver extends Keyserver {
mQuery = query;
try {
- Iterable<Match> matches = Search.search(query);
+ Iterable<Match> matches = Search.search(query, proxy);
for (Match match : matches) {
results.add(makeEntry(match));
}
@@ -98,16 +99,16 @@ public class KeybaseKeyserver extends Keyserver {
}
@Override
- public String get(String id) throws QueryFailedException {
+ public String get(String id, Proxy proxy) throws QueryFailedException {
try {
- return User.keyForUsername(id);
+ return User.keyForUsername(id, proxy);
} catch (KeybaseException e) {
throw new QueryFailedException(e.getMessage());
}
}
@Override
- public void add(String armoredKey) throws AddKeyException {
+ public void add(String armoredKey, Proxy proxy) throws AddKeyException {
throw new AddKeyException();
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java
index 5e4bd0b70..640b39f44 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java
@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.keyimport;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.Proxy;
import java.util.List;
public abstract class Keyserver {
@@ -31,6 +32,7 @@ public abstract class Keyserver {
public CloudSearchFailureException(String message) {
super(message);
}
+
public CloudSearchFailureException() {
super();
}
@@ -67,12 +69,12 @@ public abstract class Keyserver {
private static final long serialVersionUID = -507574859137295530L;
}
- public abstract List<ImportKeysListEntry> search(String query) throws QueryFailedException,
+ public abstract List<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException,
QueryNeedsRepairException;
- public abstract String get(String keyIdHex) throws QueryFailedException;
+ public abstract String get(String keyIdHex, Proxy proxy) throws QueryFailedException;
- public abstract void add(String armoredKey) throws AddKeyException;
+ public abstract void add(String armoredKey, Proxy proxy) throws AddKeyException;
public static String readAll(InputStream in, String encoding) throws IOException {
ByteArrayOutputStream raw = new ByteArrayOutputStream();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java
index fae59b7a4..e4026eaaf 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java
@@ -19,7 +19,9 @@ package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import org.sufficientlysecure.keychain.Constants.key;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface;
import org.sufficientlysecure.keychain.pgp.Progressable;
@@ -76,9 +78,8 @@ public abstract class BaseOperation <T extends Parcelable> implements Passphrase
mCancelled = cancelled;
}
- public OperationResult execute(T input, CryptoInputParcel cryptoInput) {
- return null;
- }
+ @NonNull
+ public abstract OperationResult execute(T input, CryptoInputParcel cryptoInput);
public void updateProgress(int message, int current, int total) {
if (mProgressable != null) {
@@ -111,8 +112,11 @@ public abstract class BaseOperation <T extends Parcelable> implements Passphrase
@Override
public Passphrase getCachedPassphrase(long subKeyId) throws NoSecretKeyException {
try {
- long masterKeyId = mProviderHelper.getMasterKeyId(subKeyId);
- return getCachedPassphrase(masterKeyId, subKeyId);
+ if (subKeyId != key.symmetric) {
+ long masterKeyId = mProviderHelper.getMasterKeyId(subKeyId);
+ return getCachedPassphrase(masterKeyId, subKeyId);
+ }
+ return getCachedPassphrase(key.symmetric, key.symmetric);
} catch (NotFoundException e) {
throw new PassphraseCacheInterface.NoSecretKeyException();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
index 0806e6a16..eeed24db0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
@@ -18,17 +18,18 @@
package org.sufficientlysecure.keychain.operations;
import android.content.Context;
+import android.support.annotation.NonNull;
-import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
-import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
+import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
+import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface;
import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation;
import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
@@ -43,27 +44,31 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
-import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
+import org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
+import java.net.Proxy;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
-/** An operation which implements a high level user id certification operation.
- *
+/**
+ * An operation which implements a high level user id certification operation.
+ * <p/>
* This operation takes a specific CertifyActionsParcel as its input. These
* contain a masterKeyId to be used for certification, and a list of
* masterKeyIds and related user ids to certify.
*
* @see CertifyActionsParcel
- *
*/
public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
- public CertifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean cancelled) {
+ public CertifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean
+ cancelled) {
super(context, providerHelper, progressable, cancelled);
}
+ @NonNull
@Override
public CertifyResult execute(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput) {
@@ -86,14 +91,24 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
case PIN:
case PATTERN:
case PASSPHRASE:
- if (!cryptoInput.hasPassphrase()) {
+ passphrase = cryptoInput.getPassphrase();
+ if (passphrase == null) {
+ try {
+ passphrase = getCachedPassphrase(certificationKey.getKeyId(), certificationKey.getKeyId());
+ } catch (PassphraseCacheInterface.NoSecretKeyException ignored) {
+ // treat as a cache miss for error handling purposes
+ }
+ }
+
+ if (passphrase == null) {
return new CertifyResult(log,
RequiredInputParcel.createRequiredSignPassphrase(
- certificationKey.getKeyId(), certificationKey.getKeyId(), null)
+ certificationKey.getKeyId(),
+ certificationKey.getKeyId(),
+ null),
+ cryptoInput
);
}
- // certification is always with the master key id, so use that one
- passphrase = cryptoInput.getPassphrase();
break;
case PASSPHRASE_EMPTY:
@@ -101,6 +116,7 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
break;
case DIVERT_TO_CARD:
+ // the unlock operation will succeed for passphrase == null in a divertToCard key
passphrase = null;
break;
@@ -174,9 +190,9 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
}
- if ( ! allRequiredInput.isEmpty()) {
+ if (!allRequiredInput.isEmpty()) {
log.add(LogType.MSG_CRT_NFC_RETURN, 1);
- return new CertifyResult(log, allRequiredInput.build());
+ return new CertifyResult(log, allRequiredInput.build(), cryptoInput);
}
log.add(LogType.MSG_CRT_SAVING, 1);
@@ -187,11 +203,24 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
return new CertifyResult(CertifyResult.RESULT_CANCELLED, log);
}
+ // these variables are used inside the following loop, but they need to be created only once
HkpKeyserver keyServer = null;
ExportOperation exportOperation = null;
+ Proxy proxy = null;
if (parcel.keyServerUri != null) {
keyServer = new HkpKeyserver(parcel.keyServerUri);
exportOperation = new ExportOperation(mContext, mProviderHelper, mProgressable);
+ if (cryptoInput.getParcelableProxy() == null) {
+ // explicit proxy not set
+ if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
+ return new CertifyResult(null,
+ RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
+ }
+ proxy = Preferences.getPreferences(mContext).getProxyPrefs()
+ .parcelableProxy.getProxy();
+ } else {
+ proxy = cryptoInput.getParcelableProxy().getProxy();
+ }
}
// Write all certified keys into the database
@@ -200,7 +229,8 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
// Check if we were cancelled
if (checkCancelled()) {
log.add(LogType.MSG_OPERATION_CANCELLED, 0);
- return new CertifyResult(CertifyResult.RESULT_CANCELLED, log, certifyOk, certifyError, uploadOk, uploadError);
+ return new CertifyResult(CertifyResult.RESULT_CANCELLED, log, certifyOk, certifyError, uploadOk,
+ uploadError);
}
log.add(LogType.MSG_CRT_SAVE, 2,
@@ -210,12 +240,15 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
SaveKeyringResult result = mProviderHelper.savePublicKeyRing(certifiedKey);
if (exportOperation != null) {
- // TODO use subresult, get rid of try/catch!
- try {
- exportOperation.uploadKeyRingToServer(keyServer, certifiedKey);
+ ExportResult uploadResult = exportOperation.uploadKeyRingToServer(
+ keyServer,
+ certifiedKey,
+ proxy);
+ log.add(uploadResult, 2);
+
+ if (uploadResult.success()) {
uploadOk += 1;
- } catch (AddKeyException e) {
- Log.e(Constants.TAG, "error uploading key", e);
+ } else {
uploadError += 1;
}
}
@@ -227,19 +260,24 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
}
log.add(result, 2);
-
}
if (certifyOk == 0) {
log.add(LogType.MSG_CRT_ERROR_NOTHING, 0);
- return new CertifyResult(CertifyResult.RESULT_ERROR, log, certifyOk, certifyError, uploadOk, uploadError);
+ return new CertifyResult(CertifyResult.RESULT_ERROR, log, certifyOk, certifyError,
+ uploadOk, uploadError);
}
- log.add(LogType.MSG_CRT_SUCCESS, 0);
- //since only verified keys are synced to contacts, we need to initiate a sync now
+ // since only verified keys are synced to contacts, we need to initiate a sync now
ContactSyncAdapterService.requestSync();
-
- return new CertifyResult(CertifyResult.RESULT_OK, log, certifyOk, certifyError, uploadOk, uploadError);
+
+ log.add(LogType.MSG_CRT_SUCCESS, 0);
+ if (uploadError != 0) {
+ return new CertifyResult(CertifyResult.RESULT_WARNINGS, log, certifyOk, certifyError, uploadOk,
+ uploadError);
+ } else {
+ return new CertifyResult(CertifyResult.RESULT_OK, log, certifyOk, certifyError, uploadOk, uploadError);
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java
index bda574e0a..782cd6800 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java
@@ -20,6 +20,7 @@
package org.sufficientlysecure.keychain.operations;
import android.content.Context;
+import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
@@ -34,6 +35,7 @@ public class ConsolidateOperation extends BaseOperation<ConsolidateInputParcel>
super(context, providerHelper, progressable);
}
+ @NonNull
@Override
public ConsolidateResult execute(ConsolidateInputParcel consolidateInputParcel,
CryptoInputParcel cryptoInputParcel) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java
index 50b2ef69b..56bd3b786 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java
@@ -18,9 +18,11 @@
package org.sufficientlysecure.keychain.operations;
import android.content.Context;
+import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.Progressable;
@@ -45,13 +47,19 @@ public class DeleteOperation extends BaseOperation<DeleteKeyringParcel> {
super(context, providerHelper, progressable);
}
+ @NonNull
@Override
- public DeleteResult execute(DeleteKeyringParcel deleteKeyringParcel,
+ public OperationResult execute(DeleteKeyringParcel deleteKeyringParcel,
CryptoInputParcel cryptoInputParcel) {
long[] masterKeyIds = deleteKeyringParcel.mMasterKeyIds;
boolean isSecret = deleteKeyringParcel.mIsSecret;
+ return onlyDeleteKey(masterKeyIds, isSecret);
+ }
+
+ private DeleteResult onlyDeleteKey(long[] masterKeyIds, boolean isSecret) {
+
OperationLog log = new OperationLog();
if (masterKeyIds.length == 0) {
@@ -111,7 +119,6 @@ public class DeleteOperation extends BaseOperation<DeleteKeyringParcel> {
}
return new DeleteResult(result, log, success, fail);
-
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
index da0aef018..f5ba88502 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
@@ -18,10 +18,12 @@
package org.sufficientlysecure.keychain.operations;
import android.content.Context;
+import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
-import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.operations.results.ExportResult;
+import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
@@ -33,16 +35,20 @@ import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
+import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ProgressScaler;
+import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
-/** An operation which implements a high level key edit operation.
- *
+/**
+ * An operation which implements a high level key edit operation.
+ * <p/>
* This operation provides a higher level interface to the edit and
* create key operations in PgpKeyOperation. It takes care of fetching
* and saving the key before and after the operation.
@@ -57,7 +63,16 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
super(context, providerHelper, progressable, cancelled);
}
- public OperationResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
+ /**
+ * Saves an edited key, and uploads it to a server atomically or otherwise as
+ * specified in saveParcel
+ *
+ * @param saveParcel primary input to the operation
+ * @param cryptoInput input that changes if user interaction is required
+ * @return the result of the operation
+ */
+ @NonNull
+ public InputPendingResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_ED, 0);
@@ -118,6 +133,36 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
// It's a success, so this must be non-null now
UncachedKeyRing ring = modifyResult.getRing();
+ if (saveParcel.isUpload()) {
+ UncachedKeyRing publicKeyRing;
+ try {
+ publicKeyRing = ring.extractPublicKeyRing();
+ } catch (IOException e) {
+ log.add(LogType.MSG_ED_ERROR_EXTRACTING_PUBLIC_UPLOAD, 1);
+ return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ }
+
+ ExportKeyringParcel exportKeyringParcel =
+ new ExportKeyringParcel(saveParcel.getUploadKeyserver(),
+ publicKeyRing);
+
+ ExportResult uploadResult =
+ new ExportOperation(mContext, mProviderHelper, mProgressable)
+ .execute(exportKeyringParcel, cryptoInput);
+
+ if (uploadResult.isPending()) {
+ return uploadResult;
+ } else if (!uploadResult.success() && saveParcel.isUploadAtomic()) {
+ // if atomic, update fail implies edit operation should also fail and not save
+ log.add(uploadResult, 2);
+ return new EditKeyResult(log, RequiredInputParcel.createRetryUploadOperation(),
+ cryptoInput);
+ } else {
+ // upload succeeded or not atomic so we continue
+ log.add(uploadResult, 2);
+ }
+ }
+
// Save the new keyring.
SaveKeyringResult saveResult = mProviderHelper
.saveSecretKeyRing(ring, new ProgressScaler(mProgressable, 60, 95, 100));
@@ -129,15 +174,24 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
}
// There is a new passphrase - cache it
- if (saveParcel.mNewUnlock != null) {
+ if (saveParcel.mNewUnlock != null && cryptoInput.mCachePassphrase) {
log.add(LogType.MSG_ED_CACHING_NEW, 1);
- PassphraseCacheService.addCachedPassphrase(mContext,
- ring.getMasterKeyId(),
- ring.getMasterKeyId(),
- saveParcel.mNewUnlock.mNewPassphrase != null
- ? saveParcel.mNewUnlock.mNewPassphrase
- : saveParcel.mNewUnlock.mNewPin,
- ring.getPublicKey().getPrimaryUserIdWithFallback());
+
+ // NOTE: Don't cache empty passphrases! Important for MOVE_KEY_TO_CARD
+ if (saveParcel.mNewUnlock.mNewPassphrase != null
+ && ( ! saveParcel.mNewUnlock.mNewPassphrase.isEmpty())) {
+ PassphraseCacheService.addCachedPassphrase(mContext,
+ ring.getMasterKeyId(),
+ ring.getMasterKeyId(),
+ saveParcel.mNewUnlock.mNewPassphrase,
+ ring.getPublicKey().getPrimaryUserIdWithFallback());
+ } else if (saveParcel.mNewUnlock.mNewPin != null) {
+ PassphraseCacheService.addCachedPassphrase(mContext,
+ ring.getMasterKeyId(),
+ ring.getMasterKeyId(),
+ saveParcel.mNewUnlock.mNewPin,
+ ring.getPublicKey().getPrimaryUserIdWithFallback());
+ }
}
updateProgress(R.string.progress_done, 100, 100);
@@ -149,5 +203,4 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
return new EditKeyResult(EditKeyResult.RESULT_OK, log, ring.getMasterKeyId());
}
-
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java
index 01a45bc79..a5b70a41f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java
@@ -18,9 +18,22 @@
package org.sufficientlysecure.keychain.operations;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Proxy;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.Constants;
@@ -40,18 +53,12 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Log;
-
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.concurrent.atomic.AtomicBoolean;
+import org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
/**
* An operation class which implements high level export
@@ -62,7 +69,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
* @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries()
* For the export operation, the input consists of a set of key ids and
* either the name of a file or an output uri to write to.
- * TODO rework uploadKeyRingToServer
*/
public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
@@ -76,26 +82,43 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
super(context, providerHelper, progressable, cancelled);
}
- public void uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring)
- throws AddKeyException {
- uploadKeyRingToServer(server, keyring.getUncachedKeyRing());
+ public ExportResult uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring,
+ Proxy proxy) {
+ return uploadKeyRingToServer(server, keyring.getUncachedKeyRing(), proxy);
}
- public void uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring) throws
- AddKeyException {
+ public ExportResult uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring, Proxy proxy) {
+ mProgressable.setProgress(R.string.progress_uploading, 0, 1);
+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ArmoredOutputStream aos = null;
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_EXPORT_UPLOAD_PUBLIC, 0, KeyFormattingUtils.convertKeyIdToHex(
+ keyring.getPublicKey().getKeyId()
+ ));
+
try {
aos = new ArmoredOutputStream(bos);
keyring.encode(aos);
aos.close();
String armoredKey = bos.toString("UTF-8");
- server.add(armoredKey);
+ server.add(armoredKey, proxy);
+
+ log.add(LogType.MSG_EXPORT_UPLOAD_SUCCESS, 1);
+ return new ExportResult(ExportResult.RESULT_OK, log);
} catch (IOException e) {
Log.e(Constants.TAG, "IOException", e);
- throw new AddKeyException();
+
+ log.add(LogType.MSG_EXPORT_ERROR_KEY, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+ } catch (AddKeyException e) {
+ Log.e(Constants.TAG, "AddKeyException", e);
+
+ log.add(LogType.MSG_EXPORT_ERROR_UPLOAD, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
} finally {
+ mProgressable.setProgress(R.string.progress_uploading, 1, 1);
try {
if (aos != null) {
aos.close();
@@ -192,21 +215,21 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
Cursor cursor = null;
try {
- String selection = null, ids[] = null;
+ String selection = null, selectionArgs[] = null;
if (masterKeyIds != null) {
- // generate placeholders and string selection args
- ids = new String[masterKeyIds.length];
- StringBuilder placeholders = new StringBuilder("?");
+ // convert long[] to String[]
+ selectionArgs = new String[masterKeyIds.length];
for (int i = 0; i < masterKeyIds.length; i++) {
- ids[i] = Long.toString(masterKeyIds[i]);
- if (i != 0) {
- placeholders.append(",?");
- }
+ selectionArgs[i] = Long.toString(masterKeyIds[i]);
}
+ // generates ?,?,? as placeholders for selectionArgs
+ String placeholders = TextUtils.join(",",
+ Collections.nCopies(masterKeyIds.length, "?"));
+
// put together selection string
- selection = Tables.KEY_RINGS_PUBLIC + "." + KeyRings.MASTER_KEY_ID
+ selection = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
+ " IN (" + placeholders + ")";
}
@@ -214,7 +237,7 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
KeyRings.buildUnifiedKeyRingsUri(), new String[]{
KeyRings.MASTER_KEY_ID, KeyRings.PUBKEY_DATA,
KeyRings.PRIVKEY_DATA, KeyRings.HAS_ANY_SECRET
- }, selection, ids, Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
+ }, selection, selectionArgs, Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
);
if (cursor == null || !cursor.moveToFirst()) {
@@ -311,20 +334,37 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
}
+ @NonNull
public ExportResult execute(ExportKeyringParcel exportInput, CryptoInputParcel cryptoInput) {
switch (exportInput.mExportType) {
case UPLOAD_KEYSERVER: {
+ Proxy proxy;
+ if (cryptoInput.getParcelableProxy() == null) {
+ // explicit proxy not set
+ if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
+ return new ExportResult(null,
+ RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
+ }
+ proxy = Preferences.getPreferences(mContext).getProxyPrefs()
+ .parcelableProxy.getProxy();
+ } else {
+ proxy = cryptoInput.getParcelableProxy().getProxy();
+ }
+
HkpKeyserver hkpKeyserver = new HkpKeyserver(exportInput.mKeyserver);
try {
- CanonicalizedPublicKeyRing keyring
- = mProviderHelper.getCanonicalizedPublicKeyRing(
- exportInput.mCanonicalizedPublicKeyringUri);
- uploadKeyRingToServer(hkpKeyserver, keyring);
- // TODO: replace with proper log
- return new ExportResult(ExportResult.RESULT_OK, new OperationLog());
- } catch (Exception e) {
+ if (exportInput.mCanonicalizedPublicKeyringUri != null) {
+ CanonicalizedPublicKeyRing keyring
+ = mProviderHelper.getCanonicalizedPublicKeyRing(
+ exportInput.mCanonicalizedPublicKeyringUri);
+ return uploadKeyRingToServer(hkpKeyserver, keyring, proxy);
+ } else {
+ return uploadKeyRingToServer(hkpKeyserver, exportInput.mUncachedKeyRing,
+ proxy);
+ }
+ } catch (ProviderHelper.NotFoundException e) {
+ Log.e(Constants.TAG, "error uploading key", e);
return new ExportResult(ExportResult.RESULT_ERROR, new OperationLog());
- // TODO: Implement better exception handling, replace with log
}
}
case EXPORT_FILE: {
@@ -335,8 +375,8 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
return exportToUri(exportInput.mMasterKeyIds, exportInput.mExportSecret,
exportInput.mOutputUri);
}
- default: { // can't happen
- return null;
+ default: { // can never happen, all enum types must be handled above
+ throw new AssertionError("must not happen, this is a bug!");
}
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java
index ace059dac..4acfd6e30 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java
@@ -18,7 +18,24 @@
package org.sufficientlysecure.keychain.operations;
+
+import java.io.IOException;
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import android.content.Context;
+import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -39,24 +56,13 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
+import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.ProgressScaler;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorCompletionService;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
/**
* An operation class which implements high level import
@@ -89,39 +95,40 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
// Overloaded functions for using progressable supplied in constructor during import
public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
- String keyServerUri) {
- return serialKeyRingImport(entries, num, keyServerUri, mProgressable);
+ String keyServerUri, Proxy proxy) {
+ return serialKeyRingImport(entries, num, keyServerUri, mProgressable, proxy);
}
public ImportKeyResult serialKeyRingImport(List<ParcelableKeyRing> entries,
- String keyServerUri) {
+ String keyServerUri, Proxy proxy) {
Iterator<ParcelableKeyRing> it = entries.iterator();
int numEntries = entries.size();
- return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable);
+ return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable, proxy);
}
public ImportKeyResult serialKeyRingImport(List<ParcelableKeyRing> entries, String keyServerUri,
- Progressable progressable) {
+ Progressable progressable, Proxy proxy) {
Iterator<ParcelableKeyRing> it = entries.iterator();
int numEntries = entries.size();
- return serialKeyRingImport(it, numEntries, keyServerUri, progressable);
+ return serialKeyRingImport(it, numEntries, keyServerUri, progressable, proxy);
}
+ @NonNull
public ImportKeyResult serialKeyRingImport(ParcelableFileCache<ParcelableKeyRing> cache,
- String keyServerUri) {
+ String keyServerUri, Proxy proxy) {
// get entries from cached file
try {
IteratorWithSize<ParcelableKeyRing> it = cache.readCache();
int numEntries = it.getSize();
- return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable);
+ return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable, proxy);
} catch (IOException e) {
// Special treatment here, we need a lot
@@ -143,11 +150,14 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
* @param keyServerUri contains uri of keyserver to import from, if it is an import from cloud
* @param progressable Allows multi-threaded import to supply a progressable that ignores the
* progress of a single key being imported
- * @return
*/
+ @NonNull
public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
- String keyServerUri, Progressable progressable) {
- updateProgress(R.string.progress_importing, 0, 100);
+ String keyServerUri, Progressable progressable,
+ Proxy proxy) {
+ if (progressable != null) {
+ progressable.setProgress(R.string.progress_importing, 0, 100);
+ }
OperationLog log = new OperationLog();
log.add(LogType.MSG_IMPORT, 0, num);
@@ -208,10 +218,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
if (entry.mExpectedFingerprint != null) {
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, "0x" +
entry.mExpectedFingerprint.substring(24));
- data = keyServer.get("0x" + entry.mExpectedFingerprint).getBytes();
+ data = keyServer.get("0x" + entry.mExpectedFingerprint, proxy)
+ .getBytes();
} else {
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, entry.mKeyIdHex);
- data = keyServer.get(entry.mKeyIdHex).getBytes();
+ data = keyServer.get(entry.mKeyIdHex, proxy).getBytes();
}
key = UncachedKeyRing.decodeFromData(data);
if (key != null) {
@@ -234,7 +245,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
try {
log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.mKeybaseName);
- byte[] data = keybaseServer.get(entry.mKeybaseName).getBytes();
+ byte[] data = keybaseServer.get(entry.mKeybaseName, proxy).getBytes();
UncachedKeyRing keybaseKey = UncachedKeyRing.decodeFromData(data);
// If there already is a key, merge the two
@@ -373,12 +384,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
importedMasterKeyIdsArray);
}
+ @NonNull
@Override
- public ImportKeyResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) {
- return importKeys(importInput.mKeyList, importInput.mKeyserver);
- }
-
- public ImportKeyResult importKeys(ArrayList<ParcelableKeyRing> keyList, String keyServer) {
+ public OperationResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) {
+ ArrayList<ParcelableKeyRing> keyList = importInput.mKeyList;
+ String keyServer = importInput.mKeyserver;
ImportKeyResult result;
@@ -386,8 +396,21 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
ParcelableFileCache<ParcelableKeyRing> cache = new ParcelableFileCache<>(mContext,
"key_import.pcl");
- result = serialKeyRingImport(cache, keyServer);
+ result = serialKeyRingImport(cache, null, null);
} else {
+ Proxy proxy;
+ if (cryptoInput.getParcelableProxy() == null) {
+ // explicit proxy not set
+ if(!OrbotHelper.isOrbotInRequiredState(mContext)) {
+ // show dialog to enable/install dialog
+ return new ImportKeyResult(null,
+ RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
+ }
+ proxy = Preferences.getPreferences(mContext).getProxyPrefs().parcelableProxy
+ .getProxy();
+ } else {
+ proxy = cryptoInput.getParcelableProxy().getProxy();
+ }
// if there is more than one key with the same fingerprint, we do a serial import to
// prevent
// https://github.com/open-keychain/open-keychain/issues/1221
@@ -397,9 +420,10 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
}
if (keyFingerprintSet.size() == keyList.size()) {
// all keys have unique fingerprints
- result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer);
+ result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer,
+ proxy);
} else {
- result = serialKeyRingImport(keyList, keyServer);
+ result = serialKeyRingImport(keyList, keyServer, proxy);
}
}
@@ -407,8 +431,10 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
return result;
}
+ @NonNull
private ImportKeyResult multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator,
- int totKeys, final String keyServer) {
+ int totKeys, final String keyServer,
+ final Proxy proxy) {
Log.d(Constants.TAG, "Multi-threaded key import starting");
if (keyListIterator != null) {
KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable);
@@ -421,7 +447,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
new SynchronousQueue<Runnable>());
ExecutorCompletionService<ImportKeyResult> importCompletionService =
- new ExecutorCompletionService(importExecutor);
+ new ExecutorCompletionService<>(importExecutor);
while (keyListIterator.hasNext()) { // submit all key rings to be imported
@@ -436,7 +462,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
ArrayList<ParcelableKeyRing> list = new ArrayList<>();
list.add(pkRing);
- return serialKeyRingImport(list, keyServer, ignoreProgressable);
+ return serialKeyRingImport(list, keyServer, ignoreProgressable, proxy);
}
};
@@ -562,4 +588,4 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
}
}
-} \ No newline at end of file
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java
index 57b99951d..30f37dd4f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java
@@ -19,7 +19,15 @@
package org.sufficientlysecure.keychain.operations;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+
import android.content.Context;
+import android.support.annotation.NonNull;
import com.textuality.keybase.lib.Proof;
import com.textuality.keybase.lib.prover.Prover;
@@ -41,11 +49,9 @@ import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificationParcel> {
@@ -54,9 +60,22 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat
super(context, providerHelper, progressable);
}
+ @NonNull
@Override
public KeybaseVerificationResult execute(KeybaseVerificationParcel keybaseInput,
CryptoInputParcel cryptoInput) {
+ Proxy proxy;
+ if (cryptoInput.getParcelableProxy() == null) {
+ // explicit proxy not set
+ if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
+ return new KeybaseVerificationResult(null,
+ RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
+ }
+ proxy = Preferences.getPreferences(mContext).getProxyPrefs()
+ .parcelableProxy.getProxy();
+ } else {
+ proxy = cryptoInput.getParcelableProxy().getProxy();
+ }
String requiredFingerprint = keybaseInput.mRequiredFingerprint;
@@ -76,7 +95,7 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
}
- if (!prover.fetchProofData()) {
+ if (!prover.fetchProofData(proxy)) {
log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_FETCH_PROOF, 1);
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
}
@@ -96,7 +115,7 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
}
Record[] records = dnsQuery.getAnswers();
- List<List<byte[]>> extents = new ArrayList<List<byte[]>>();
+ List<List<byte[]>> extents = new ArrayList<>();
for (Record r : records) {
Data d = r.getPayload();
if (d instanceof TXT) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java
index efe0c466a..2f25b6926 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java
@@ -17,7 +17,11 @@
package org.sufficientlysecure.keychain.operations;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
import android.content.Context;
+import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
@@ -29,7 +33,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
-import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.PromoteKeyringParcel;
@@ -37,9 +40,6 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ProgressScaler;
-import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicBoolean;
-
/** An operation which promotes a public key ring to a secret one.
*
* This operation can only be applied to public key rings where no secret key
@@ -54,18 +54,10 @@ public class PromoteKeyOperation extends BaseOperation<PromoteKeyringParcel> {
super(context, providerHelper, progressable, cancelled);
}
+ @NonNull
@Override
public PromoteKeyResult execute(PromoteKeyringParcel promoteKeyringParcel,
CryptoInputParcel cryptoInputParcel) {
- // Input
- long masterKeyId = promoteKeyringParcel.mKeyRingId;
- byte[] cardAid = promoteKeyringParcel.mCardAid;
- long[] subKeyIds = promoteKeyringParcel.mSubKeyIds;
-
- return execute(masterKeyId, cardAid, subKeyIds);
- }
-
- public PromoteKeyResult execute(long masterKeyId, byte[] cardAid, long[] subKeyIds) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_PR, 0);
@@ -76,17 +68,17 @@ public class PromoteKeyOperation extends BaseOperation<PromoteKeyringParcel> {
try {
log.add(LogType.MSG_PR_FETCHING, 1,
- KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
+ KeyFormattingUtils.convertKeyIdToHex(promoteKeyringParcel.mKeyRingId));
CanonicalizedPublicKeyRing pubRing =
- mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
+ mProviderHelper.getCanonicalizedPublicKeyRing(promoteKeyringParcel.mKeyRingId);
- if (subKeyIds == null) {
+ if (promoteKeyringParcel.mSubKeyIds == null) {
log.add(LogType.MSG_PR_ALL, 1);
} else {
// sort for binary search
for (CanonicalizedPublicKey key : pubRing.publicKeyIterator()) {
long subKeyId = key.getKeyId();
- if (naiveIndexOf(subKeyIds, subKeyId) != null) {
+ if (naiveIndexOf(promoteKeyringParcel.mSubKeyIds, subKeyId) != null) {
log.add(LogType.MSG_PR_SUBKEY_MATCH, 1,
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
} else {
@@ -97,7 +89,8 @@ public class PromoteKeyOperation extends BaseOperation<PromoteKeyringParcel> {
}
// create divert-to-card secret key from public key
- promotedRing = pubRing.createDivertSecretRing(cardAid, subKeyIds);
+ promotedRing = pubRing.createDivertSecretRing(promoteKeyringParcel.mCardAid,
+ promoteKeyringParcel.mSubKeyIds);
} catch (NotFoundException e) {
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java
new file mode 100644
index 000000000..ecf64e1af
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.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.operations;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.operations.results.RevokeResult;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.RevokeKeyringParcel;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class RevokeOperation extends BaseOperation<RevokeKeyringParcel> {
+
+ public RevokeOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
+ super(context, providerHelper, progressable);
+ }
+
+ @NonNull
+ @Override
+ public OperationResult execute(RevokeKeyringParcel revokeKeyringParcel,
+ CryptoInputParcel cryptoInputParcel) {
+
+ // we don't cache passphrases during revocation
+ cryptoInputParcel.mCachePassphrase = false;
+
+ long masterKeyId = revokeKeyringParcel.mMasterKeyId;
+
+ OperationResult.OperationLog log = new OperationResult.OperationLog();
+ log.add(OperationResult.LogType.MSG_REVOKE, 0,
+ KeyFormattingUtils.beautifyKeyId(masterKeyId));
+
+ try {
+
+ Uri secretUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(masterKeyId);
+ CachedPublicKeyRing keyRing = mProviderHelper.getCachedPublicKeyRing(secretUri);
+
+ // check if this is a master secret key we can work with
+ switch (keyRing.getSecretKeyType(masterKeyId)) {
+ case GNU_DUMMY:
+ log.add(OperationResult.LogType.MSG_EK_ERROR_DUMMY, 1);
+ return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
+ }
+
+ SaveKeyringParcel saveKeyringParcel = getRevokedSaveKeyringParcel(masterKeyId,
+ keyRing.getFingerprint());
+
+ // all revoke operations are made atomic as of now
+ saveKeyringParcel.setUpdateOptions(revokeKeyringParcel.mUpload, true,
+ revokeKeyringParcel.mKeyserver);
+
+ InputPendingResult revokeAndUploadResult = new EditKeyOperation(mContext,
+ mProviderHelper, mProgressable, mCancelled)
+ .execute(saveKeyringParcel, cryptoInputParcel);
+
+ if (revokeAndUploadResult.isPending()) {
+ return revokeAndUploadResult;
+ }
+
+ log.add(revokeAndUploadResult, 1);
+
+ if (revokeAndUploadResult.success()) {
+ log.add(OperationResult.LogType.MSG_REVOKE_OK, 1);
+ return new RevokeResult(RevokeResult.RESULT_OK, log, masterKeyId);
+ } else {
+ log.add(OperationResult.LogType.MSG_REVOKE_KEY_FAIL, 1);
+ return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
+ }
+
+ } catch (PgpKeyNotFoundException | ProviderHelper.NotFoundException e) {
+ Log.e(Constants.TAG, "could not find key to revoke", e);
+ log.add(OperationResult.LogType.MSG_REVOKE_KEY_FAIL, 1);
+ return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
+ }
+ }
+
+ private SaveKeyringParcel getRevokedSaveKeyringParcel(long masterKeyId, byte[] fingerprint) {
+ final String[] SUBKEYS_PROJECTION = new String[]{
+ KeychainContract.Keys.KEY_ID
+ };
+ final int INDEX_KEY_ID = 0;
+
+ Uri keysUri = KeychainContract.Keys.buildKeysUri(masterKeyId);
+ Cursor subKeyCursor =
+ mContext.getContentResolver().query(keysUri, SUBKEYS_PROJECTION, null, null, null);
+
+ SaveKeyringParcel saveKeyringParcel =
+ new SaveKeyringParcel(masterKeyId, fingerprint);
+
+ // add all subkeys, for revocation
+ while (subKeyCursor != null && subKeyCursor.moveToNext()) {
+ saveKeyringParcel.mRevokeSubKeys.add(subKeyCursor.getLong(INDEX_KEY_ID));
+ }
+ if (subKeyCursor != null) {
+ subKeyCursor.close();
+ }
+
+ final String[] USER_IDS_PROJECTION = new String[]{
+ KeychainContract.UserPackets.USER_ID
+ };
+ final int INDEX_USER_ID = 0;
+
+ Uri userIdsUri = KeychainContract.UserPackets.buildUserIdsUri(masterKeyId);
+ Cursor userIdCursor = mContext.getContentResolver().query(
+ userIdsUri, USER_IDS_PROJECTION, null, null, null);
+
+ while (userIdCursor != null && userIdCursor.moveToNext()) {
+ saveKeyringParcel.mRevokeUserIds.add(userIdCursor.getString(INDEX_USER_ID));
+ }
+ if (userIdCursor != null) {
+ userIdCursor.close();
+ }
+
+ return saveKeyringParcel;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java
index 7c58d62f8..843a55389 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java
@@ -19,13 +19,13 @@ package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.net.Uri;
+import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
-import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
@@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSign
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.ByteArrayInputStream;
@@ -62,6 +63,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
super(context, providerHelper, progressable, cancelled);
}
+ @NonNull
public SignEncryptResult execute(SignEncryptParcel input, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();
@@ -85,7 +87,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
input.getSignatureMasterKeyId()).getSecretSignId();
input.setSignatureSubKeyId(signKeyId);
} catch (PgpKeyNotFoundException e) {
- e.printStackTrace();
+ Log.e(Constants.TAG, "Key not found", e);
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
}
}
@@ -153,7 +155,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
RequiredInputParcel requiredInput = result.getRequiredInputParcel();
// Passphrase returns immediately, nfc are aggregated
if (requiredInput.mType == RequiredInputType.PASSPHRASE) {
- return new SignEncryptResult(log, requiredInput, results);
+ return new SignEncryptResult(log, requiredInput, results, cryptoInput);
}
if (pendingInputBuilder == null) {
pendingInputBuilder = new NfcSignOperationsBuilder(requiredInput.mSignatureTime,
@@ -171,7 +173,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
} while (!inputUris.isEmpty());
if (pendingInputBuilder != null && !pendingInputBuilder.isEmpty()) {
- return new SignEncryptResult(log, pendingInputBuilder.build(), results);
+ return new SignEncryptResult(log, pendingInputBuilder.build(), results, cryptoInput);
}
if (!outputUris.isEmpty()) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java
index a9f8170d9..cf73f019c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java
@@ -23,6 +23,7 @@ import android.content.Intent;
import android.os.Parcel;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
@@ -38,8 +39,9 @@ public class CertifyResult extends InputPendingResult {
super(result, log);
}
- public CertifyResult(OperationLog log, RequiredInputParcel requiredInput) {
- super(log, requiredInput);
+ public CertifyResult(OperationLog log, RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInput, cryptoInputParcel);
}
public CertifyResult(int result, OperationLog log, int certifyOk, int certifyError, int uploadOk, int uploadError) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java
index 25a86f137..f9a738d56 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java
@@ -24,7 +24,6 @@ import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
-import org.sufficientlysecure.keychain.util.Passphrase;
public class DecryptVerifyResult extends InputPendingResult {
@@ -45,8 +44,9 @@ public class DecryptVerifyResult extends InputPendingResult {
super(result, log);
}
- public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput) {
- super(log, requiredInput);
+ public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInput, cryptoInputParcel);
}
public DecryptVerifyResult(Parcel source) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java
index 52ff8bf44..1a8f10d4f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java
@@ -21,8 +21,11 @@ package org.sufficientlysecure.keychain.operations.results;
import android.app.Activity;
import android.content.Intent;
import android.os.Parcel;
+import android.support.annotation.Nullable;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
@@ -30,7 +33,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
-public class DeleteResult extends OperationResult {
+public class DeleteResult extends InputPendingResult {
final public int mOk, mFail;
@@ -40,6 +43,19 @@ public class DeleteResult extends OperationResult {
mFail = fail;
}
+ /**
+ * used when more input is required
+ * @param log operation log upto point of required input, if any
+ * @param requiredInput represents input required
+ */
+ public DeleteResult(@Nullable OperationLog log, RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInput, cryptoInputParcel);
+ // values are not to be used
+ mOk = -1;
+ mFail = -1;
+ }
+
/** Construct from a parcel - trivial because we have no extra data. */
public DeleteResult(Parcel source) {
super(source);
@@ -109,7 +125,10 @@ public class DeleteResult extends OperationResult {
} else {
duration = 0;
style = Style.ERROR;
- if (mFail == 0) {
+ if (mLog.getLast().mType == LogType.MSG_DEL_ERROR_MULTI_SECRET) {
+ str = activity.getString(R.string.secret_cannot_multiple);
+ }
+ else if (mFail == 0) {
str = activity.getString(R.string.delete_nothing);
} else {
str = activity.getResources().getQuantityString(R.plurals.delete_fail, mFail);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java
index 842b75c3b..6098d59d5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java
@@ -20,7 +20,10 @@ package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
-public class EditKeyResult extends OperationResult {
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+
+public class EditKeyResult extends InputPendingResult {
public final Long mMasterKeyId;
@@ -29,6 +32,12 @@ public class EditKeyResult extends OperationResult {
mMasterKeyId = masterKeyId;
}
+ public EditKeyResult(OperationLog log, RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInput, cryptoInputParcel);
+ mMasterKeyId = null;
+ }
+
public EditKeyResult(Parcel source) {
super(source);
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java
index c8edce259..e21ef949f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java
@@ -19,7 +19,10 @@ package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
-public class ExportResult extends OperationResult {
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+
+public class ExportResult extends InputPendingResult {
final int mOkPublic, mOkSecret;
@@ -33,6 +36,15 @@ public class ExportResult extends OperationResult {
mOkSecret = okSecret;
}
+
+ public ExportResult(OperationLog log, RequiredInputParcel requiredInputParcel,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInputParcel, cryptoInputParcel);
+ // we won't use these values
+ mOkPublic = -1;
+ mOkSecret = -1;
+ }
+
/** Construct from a parcel - trivial because we have no extra data. */
public ExportResult(Parcel source) {
super(source);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java
index 53bc545c5..bdc4d9a47 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java
@@ -20,7 +20,10 @@ package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
-public class GetKeyResult extends OperationResult {
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+
+public class GetKeyResult extends InputPendingResult {
public int mNonPgpPartsCount;
@@ -36,6 +39,11 @@ public class GetKeyResult extends OperationResult {
super(result, log);
}
+ public GetKeyResult(OperationLog log, RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInput, cryptoInputParcel);
+ }
+
public static final int RESULT_ERROR_NO_VALID_KEYS = RESULT_ERROR + 8;
public static final int RESULT_ERROR_NO_PGP_PARTS = RESULT_ERROR + 16;
public static final int RESULT_ERROR_QUERY_TOO_SHORT = RESULT_ERROR + 32;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java
index 2a032cef2..5f5090bee 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java
@@ -23,6 +23,8 @@ import android.content.Intent;
import android.os.Parcel;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
@@ -30,7 +32,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
-public class ImportKeyResult extends OperationResult {
+public class ImportKeyResult extends InputPendingResult {
public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret;
public final long[] mImportedMasterKeyIds;
@@ -80,7 +82,7 @@ public class ImportKeyResult extends OperationResult {
}
public ImportKeyResult(int result, OperationLog log) {
- this(result, log, 0, 0, 0, 0, new long[] { });
+ this(result, log, 0, 0, 0, 0, new long[]{});
}
public ImportKeyResult(int result, OperationLog log,
@@ -94,6 +96,17 @@ public class ImportKeyResult extends OperationResult {
mImportedMasterKeyIds = importedMasterKeyIds;
}
+ public ImportKeyResult(OperationLog log, RequiredInputParcel requiredInputParcel,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInputParcel, cryptoInputParcel);
+ // just assign default values, we won't use them anyway
+ mNewKeys = 0;
+ mUpdatedKeys = 0;
+ mBadKeys = 0;
+ mSecret = 0;
+ mImportedMasterKeyIds = new long[]{};
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
index 0b7aa6d03..d767382ae 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
@@ -18,10 +18,9 @@
package org.sufficientlysecure.keychain.operations.results;
-import java.util.ArrayList;
-
import android.os.Parcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
public class InputPendingResult extends OperationResult {
@@ -30,26 +29,33 @@ public class InputPendingResult extends OperationResult {
public static final int RESULT_PENDING = RESULT_ERROR + 8;
final RequiredInputParcel mRequiredInput;
+ // in case operation needs to add to/changes the cryptoInputParcel sent to it
+ public final CryptoInputParcel mCryptoInputParcel;
public InputPendingResult(int result, OperationLog log) {
super(result, log);
mRequiredInput = null;
+ mCryptoInputParcel = null;
}
- public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput) {
+ public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInputParcel) {
super(RESULT_PENDING, log);
mRequiredInput = requiredInput;
+ mCryptoInputParcel = cryptoInputParcel;
}
public InputPendingResult(Parcel source) {
super(source);
mRequiredInput = source.readParcelable(getClass().getClassLoader());
+ mCryptoInputParcel = source.readParcelable(getClass().getClassLoader());
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(mRequiredInput, 0);
+ dest.writeParcelable(mCryptoInputParcel, 0);
}
public boolean isPending() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java
index 420cbbf01..84648d32c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java
@@ -24,7 +24,10 @@ import android.os.Parcelable;
import com.textuality.keybase.lib.KeybaseException;
import com.textuality.keybase.lib.prover.Prover;
-public class KeybaseVerificationResult extends OperationResult implements Parcelable {
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+
+public class KeybaseVerificationResult extends InputPendingResult {
public final String mProofUrl;
public final String mPresenceUrl;
public final String mPresenceLabel;
@@ -44,6 +47,14 @@ public class KeybaseVerificationResult extends OperationResult implements Parcel
mPresenceLabel = prover.getPresenceLabel();
}
+ public KeybaseVerificationResult(OperationLog log, RequiredInputParcel requiredInputParcel,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInputParcel, cryptoInputParcel);
+ mProofUrl = null;
+ mPresenceUrl = null;
+ mPresenceLabel = null;
+ }
+
protected KeybaseVerificationResult(Parcel in) {
super(in);
mProofUrl = in.readString();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
index f0561bef2..04013e9ed 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
@@ -482,6 +482,7 @@ public abstract class OperationResult implements Parcelable {
// secret key modify
MSG_MF (LogLevel.START, R.string.msg_mr),
MSG_MF_DIVERT (LogLevel.DEBUG, R.string.msg_mf_divert),
+ MSG_MF_ERROR_DIVERT_NEWSUB (LogLevel.ERROR, R.string.msg_mf_error_divert_newsub),
MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial),
MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode),
MSG_MF_ERROR_FINGERPRINT (LogLevel.ERROR, R.string.msg_mf_error_fingerprint),
@@ -499,6 +500,7 @@ public abstract class OperationResult implements Parcelable {
MSG_MF_ERROR_RESTRICTED(LogLevel.ERROR, R.string.msg_mf_error_restricted),
MSG_MF_ERROR_REVOKED_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_revoked_primary),
MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig),
+ MSG_MF_ERROR_SUB_STRIPPED(LogLevel.ERROR, R.string.msg_mf_error_sub_stripped),
MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing),
MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS(LogLevel.ERROR, R.string.msg_mf_error_conflicting_nfc_commands),
MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT(LogLevel.ERROR, R.string.msg_mf_error_duplicate_keytocard_for_slot),
@@ -510,6 +512,8 @@ public abstract class OperationResult implements Parcelable {
MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin),
MSG_MF_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty),
MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase),
+ MSG_MF_PIN (LogLevel.INFO, R.string.msg_mf_pin),
+ MSG_MF_ADMIN_PIN (LogLevel.INFO, R.string.msg_mf_admin_pin),
MSG_MF_PASSPHRASE_KEY (LogLevel.DEBUG, R.string.msg_mf_passphrase_key),
MSG_MF_PASSPHRASE_EMPTY_RETRY (LogLevel.DEBUG, R.string.msg_mf_passphrase_empty_retry),
MSG_MF_PASSPHRASE_FAIL (LogLevel.WARN, R.string.msg_mf_passphrase_fail),
@@ -567,6 +571,8 @@ public abstract class OperationResult implements Parcelable {
MSG_ED_CACHING_NEW (LogLevel.DEBUG, R.string.msg_ed_caching_new),
MSG_ED_ERROR_NO_PARCEL (LogLevel.ERROR, R.string.msg_ed_error_no_parcel),
MSG_ED_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_ed_error_key_not_found),
+ MSG_ED_ERROR_EXTRACTING_PUBLIC_UPLOAD (LogLevel.ERROR,
+ R.string.msg_ed_error_extract_public_upload),
MSG_ED_FETCHING (LogLevel.DEBUG, R.string.msg_ed_fetching),
MSG_ED_SUCCESS (LogLevel.OK, R.string.msg_ed_success),
@@ -601,12 +607,14 @@ public abstract class OperationResult implements Parcelable {
MSG_DC_CLEAR_SIGNATURE_OK (LogLevel.OK, R.string.msg_dc_clear_signature_ok),
MSG_DC_CLEAR_SIGNATURE (LogLevel.DEBUG, R.string.msg_dc_clear_signature),
MSG_DC_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_dc_error_bad_passphrase),
+ MSG_DC_ERROR_SYM_PASSPHRASE (LogLevel.ERROR, R.string.msg_dc_error_sym_passphrase),
MSG_DC_ERROR_CORRUPT_DATA (LogLevel.ERROR, R.string.msg_dc_error_corrupt_data),
MSG_DC_ERROR_EXTRACT_KEY (LogLevel.ERROR, R.string.msg_dc_error_extract_key),
MSG_DC_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_dc_error_integrity_check),
MSG_DC_ERROR_INTEGRITY_MISSING (LogLevel.ERROR, R.string.msg_dc_error_integrity_missing),
MSG_DC_ERROR_INVALID_DATA (LogLevel.ERROR, R.string.msg_dc_error_invalid_data),
MSG_DC_ERROR_IO (LogLevel.ERROR, R.string.msg_dc_error_io),
+ MSG_DC_ERROR_INPUT (LogLevel.ERROR, R.string.msg_dc_error_input),
MSG_DC_ERROR_NO_DATA (LogLevel.ERROR, R.string.msg_dc_error_no_data),
MSG_DC_ERROR_NO_KEY (LogLevel.ERROR, R.string.msg_dc_error_no_key),
MSG_DC_ERROR_PGP_EXCEPTION (LogLevel.ERROR, R.string.msg_dc_error_pgp_exception),
@@ -690,6 +698,7 @@ public abstract class OperationResult implements Parcelable {
MSG_CRT_WARN_NOT_FOUND (LogLevel.WARN, R.string.msg_crt_warn_not_found),
MSG_CRT_WARN_CERT_FAILED (LogLevel.WARN, R.string.msg_crt_warn_cert_failed),
MSG_CRT_WARN_SAVE_FAILED (LogLevel.WARN, R.string.msg_crt_warn_save_failed),
+ MSG_CRT_WARN_UPLOAD_FAILED (LogLevel.WARN, R.string.msg_crt_warn_upload_failed),
MSG_IMPORT (LogLevel.START, R.plurals.msg_import),
@@ -710,6 +719,7 @@ public abstract class OperationResult implements Parcelable {
MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success),
MSG_EXPORT (LogLevel.START, R.plurals.msg_export),
+ MSG_EXPORT_UPLOAD_PUBLIC (LogLevel.START, R.string.msg_export_upload_public),
MSG_EXPORT_PUBLIC (LogLevel.DEBUG, R.string.msg_export_public),
MSG_EXPORT_SECRET (LogLevel.DEBUG, R.string.msg_export_secret),
MSG_EXPORT_ALL (LogLevel.START, R.string.msg_export_all),
@@ -721,7 +731,9 @@ public abstract class OperationResult implements Parcelable {
MSG_EXPORT_ERROR_DB (LogLevel.ERROR, R.string.msg_export_error_db),
MSG_EXPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_export_error_io),
MSG_EXPORT_ERROR_KEY (LogLevel.ERROR, R.string.msg_export_error_key),
+ MSG_EXPORT_ERROR_UPLOAD (LogLevel.ERROR, R.string.msg_export_error_upload),
MSG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_success),
+ MSG_EXPORT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_export_upload_success),
MSG_CRT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_crt_upload_success),
@@ -742,7 +754,7 @@ public abstract class OperationResult implements Parcelable {
MSG_GET_QUERY_FAILED(LogLevel.ERROR, R.string.msg_download_query_failed),
MSG_DEL_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_del_error_empty),
- MSG_DEL_ERROR_MULTI_SECRET (LogLevel.DEBUG, R.string.msg_del_error_multi_secret),
+ MSG_DEL_ERROR_MULTI_SECRET (LogLevel.ERROR, R.string.msg_del_error_multi_secret),
MSG_DEL (LogLevel.START, R.plurals.msg_del),
MSG_DEL_KEY (LogLevel.DEBUG, R.string.msg_del_key),
MSG_DEL_KEY_FAIL (LogLevel.WARN, R.string.msg_del_key_fail),
@@ -750,6 +762,13 @@ public abstract class OperationResult implements Parcelable {
MSG_DEL_OK (LogLevel.OK, R.plurals.msg_del_ok),
MSG_DEL_FAIL (LogLevel.WARN, R.plurals.msg_del_fail),
+ MSG_REVOKE_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_revoke_error_empty),
+ MSG_REVOKE_ERROR_MULTI_SECRET (LogLevel.DEBUG, R.string.msg_revoke_error_multi_secret),
+ MSG_REVOKE_ERROR_NOT_FOUND (LogLevel.DEBUG, R.string.msg_revoke_error_multi_secret),
+ MSG_REVOKE (LogLevel.DEBUG, R.string.msg_revoke_key),
+ MSG_REVOKE_KEY_FAIL (LogLevel.ERROR, R.string.msg_revoke_key_fail),
+ MSG_REVOKE_OK (LogLevel.OK, R.string.msg_revoke_ok),
+
// keybase verification
MSG_KEYBASE_VERIFICATION(LogLevel.START, R.string.msg_keybase_verification),
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java
index 38edbf6ee..30307ba46 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java
@@ -22,6 +22,7 @@ import android.os.Parcel;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
@@ -37,8 +38,9 @@ public class PgpEditKeyResult extends InputPendingResult {
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none;
}
- public PgpEditKeyResult(OperationLog log, RequiredInputParcel requiredInput) {
- super(log, requiredInput);
+ public PgpEditKeyResult(OperationLog log, RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInput, cryptoInputParcel);
mRingMasterKeyId = Constants.key.none;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java
index acb265462..2b33b8ace 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java
@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
@@ -38,8 +39,9 @@ public class PgpSignEncryptResult extends InputPendingResult {
super(result, log);
}
- public PgpSignEncryptResult(OperationLog log, RequiredInputParcel requiredInput) {
- super(log, requiredInput);
+ public PgpSignEncryptResult(OperationLog log, RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInput, cryptoInputParcel);
}
public PgpSignEncryptResult(Parcel source) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/RevokeResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/RevokeResult.java
new file mode 100644
index 000000000..b737f6e50
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/RevokeResult.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.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.operations.results;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
+import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+
+public class RevokeResult extends InputPendingResult {
+
+ public final long mMasterKeyId;
+
+ public RevokeResult(int result, OperationLog log, long masterKeyId) {
+ super(result, log);
+ mMasterKeyId = masterKeyId;
+ }
+
+ /**
+ * used when more input is required
+ *
+ * @param log operation log upto point of required input, if any
+ * @param requiredInput represents input required
+ */
+ @SuppressWarnings("unused") // standard pattern across all results, we might need it later
+ public RevokeResult(@Nullable OperationLog log, RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInput, cryptoInputParcel);
+ // we won't use these values
+ mMasterKeyId = -1;
+ }
+
+ /** Construct from a parcel - trivial because we have no extra data. */
+ public RevokeResult(Parcel source) {
+ super(source);
+ mMasterKeyId = source.readLong();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeLong(mMasterKeyId);
+ }
+
+ public static final Parcelable.Creator<RevokeResult> CREATOR = new Parcelable.Creator<RevokeResult>() {
+ @Override
+ public RevokeResult createFromParcel(Parcel in) {
+ return new RevokeResult(in);
+ }
+
+ @Override
+ public RevokeResult[] newArray(int size) {
+ return new RevokeResult[size];
+ }
+ };
+
+ @Override
+ public Notify.Showable createNotify(final Activity activity) {
+
+ int resultType = getResult();
+
+ String str;
+ int duration;
+ Notify.Style style;
+
+ // Not an overall failure
+ if ((resultType & OperationResult.RESULT_ERROR) == 0) {
+
+ duration = Notify.LENGTH_LONG;
+
+ // New and updated keys
+ if (resultType == OperationResult.RESULT_OK) {
+ style = Notify.Style.OK;
+ str = activity.getString(R.string.revoke_ok);
+ } else {
+ duration = 0;
+ style = Notify.Style.ERROR;
+ str = "internal error";
+ }
+
+ } else {
+ duration = 0;
+ style = Notify.Style.ERROR;
+ str = activity.getString(R.string.revoke_fail);
+ }
+
+ return Notify.create(activity, str, duration, style, new Notify.ActionListener() {
+ @Override
+ public void onAction() {
+ Intent intent = new Intent(
+ activity, LogDisplayActivity.class);
+ intent.putExtra(LogDisplayFragment.EXTRA_RESULT, RevokeResult.this);
+ activity.startActivity(intent);
+ }
+ }, R.string.snackbar_details);
+
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java
index b05921b0d..0e0c5d598 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java
@@ -19,18 +19,20 @@ package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
-import java.util.ArrayList;
-
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import java.util.ArrayList;
public class SignEncryptResult extends InputPendingResult {
ArrayList<PgpSignEncryptResult> mResults;
byte[] mResultBytes;
- public SignEncryptResult(OperationLog log, RequiredInputParcel requiredInput, ArrayList<PgpSignEncryptResult> results) {
- super(log, requiredInput);
+ public SignEncryptResult(OperationLog log, RequiredInputParcel requiredInput,
+ ArrayList<PgpSignEncryptResult> results,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInput, cryptoInputParcel);
mResults = results;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
index 432ba23e9..770e8de91 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
@@ -27,6 +27,9 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
/** A generic wrapped PGPKeyRing object.
*
@@ -91,6 +94,16 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
return getRing().getPublicKey().isEncryptionKey();
}
+ public Set<Long> getEncryptIds() {
+ HashSet<Long> result = new HashSet<>();
+ for(CanonicalizedPublicKey key : publicKeyIterator()) {
+ if (key.canEncrypt() && key.isValid()) {
+ result.add(key.getKeyId());
+ }
+ }
+ return result;
+ }
+
public long getEncryptId() throws PgpKeyNotFoundException {
for(CanonicalizedPublicKey key : publicKeyIterator()) {
if (key.canEncrypt() && key.isValid()) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
index 676491164..be5f21f23 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
@@ -62,19 +62,6 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
return mRing;
}
- /** Getter that returns the subkey that should be used for signing. */
- CanonicalizedPublicKey getEncryptionSubKey() throws PgpKeyNotFoundException {
- PGPPublicKey key = getRing().getPublicKey(getEncryptId());
- if(key != null) {
- CanonicalizedPublicKey cKey = new CanonicalizedPublicKey(this, key);
- if(!cKey.canEncrypt()) {
- throw new PgpKeyNotFoundException("key error");
- }
- return cKey;
- }
- throw new PgpKeyNotFoundException("no encryption key available");
- }
-
public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() {
@SuppressWarnings("unchecked")
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
index a6d260d22..e264b4678 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
@@ -19,12 +19,14 @@
package org.sufficientlysecure.keychain.pgp;
import android.content.Context;
+import android.support.annotation.NonNull;
import android.webkit.MimeTypeMap;
import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.openpgp.PGPCompressedData;
+import org.spongycastle.openpgp.PGPDataValidationException;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.spongycastle.openpgp.PGPEncryptedDataList;
import org.spongycastle.openpgp.PGPException;
@@ -44,7 +46,9 @@ import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
+import org.spongycastle.util.encoders.DecoderException;
import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Constants.key;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
@@ -80,9 +84,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
super(context, providerHelper, progressable);
}
- /**
- * Decrypts and/or verifies data based on parameters of class
- */
+ /** Decrypts and/or verifies data based on parameters of PgpDecryptVerifyInputParcel. */
+ @NonNull
public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput) {
InputData inputData;
OutputStream outputStream;
@@ -96,8 +99,10 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
long inputSize = FileHelper.getFileSize(mContext, input.getInputUri(), 0);
inputData = new InputData(inputStream, inputSize);
} catch (FileNotFoundException e) {
- e.printStackTrace();
- return null;
+ Log.e(Constants.TAG, "Input URI could not be opened: " + input.getInputUri(), e);
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_DC_ERROR_INPUT, 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
}
@@ -107,8 +112,10 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
try {
outputStream = mContext.getContentResolver().openOutputStream(input.getOutputUri());
} catch (FileNotFoundException e) {
- e.printStackTrace();
- return null;
+ Log.e(Constants.TAG, "Output URI could not be opened: " + input.getOutputUri(), e);
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_DC_ERROR_IO, 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
}
@@ -122,11 +129,13 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
}
+ @NonNull
public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputData inputData, OutputStream outputStream) {
return executeInternal(input, cryptoInput, inputData, outputStream);
}
+ @NonNull
private DecryptVerifyResult executeInternal(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputData inputData, OutputStream outputStream) {
try {
@@ -161,6 +170,13 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
OperationLog log = new OperationLog();
log.add(LogType.MSG_DC_ERROR_PGP_EXCEPTION, 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
+ } catch (DecoderException | ArrayIndexOutOfBoundsException e) {
+ // these can happen if assumptions in JcaPGPObjectFactory.nextObject() aren't
+ // fulfilled, so we need to catch them here to handle this gracefully
+ Log.d(Constants.TAG, "data error", e);
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_DC_ERROR_IO, 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
} catch (IOException e) {
Log.d(Constants.TAG, "IOException", e);
OperationLog log = new OperationLog();
@@ -169,9 +185,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
}
}
- /**
- * Verify Keybase.io style signed literal data
- */
+ /**Verify signed plaintext data (PGP/INLINE). */
+ @NonNull
private DecryptVerifyResult verifySignedLiteralData(
PgpDecryptVerifyInputParcel input, InputStream in, OutputStream out, int indent)
throws IOException, PGPException {
@@ -301,9 +316,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
}
- /**
- * Decrypt and/or verifies binary or ascii armored pgp
- */
+ /** Decrypt and/or verify binary or ascii armored pgp data. */
+ @NonNull
private DecryptVerifyResult decryptVerify(
PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputStream in, OutputStream out, int indent) throws IOException, PGPException {
@@ -445,7 +459,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
return new DecryptVerifyResult(log,
RequiredInputParcel.createRequiredDecryptPassphrase(
- secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()));
+ secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()),
+ cryptoInput);
}
}
@@ -473,12 +488,24 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
// if no passphrase is given, return here
// indicating that a passphrase is missing!
if (!cryptoInput.hasPassphrase()) {
- log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
- return new DecryptVerifyResult(log,
- RequiredInputParcel.createRequiredSymmetricPassphrase());
- }
- passphrase = cryptoInput.getPassphrase();
+ try {
+ passphrase = getCachedPassphrase(key.symmetric);
+ log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
+ } catch (PassphraseCacheInterface.NoSecretKeyException e) {
+ // nvm
+ }
+
+ if (passphrase == null) {
+ log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
+ return new DecryptVerifyResult(log,
+ RequiredInputParcel.createRequiredSymmetricPassphrase(),
+ cryptoInput);
+ }
+
+ } else {
+ passphrase = cryptoInput.getPassphrase();
+ }
// break out of while, only decrypt the first packet
break;
@@ -514,7 +541,14 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
passphrase.getCharArray());
- clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
+ try {
+ clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
+ } catch (PGPDataValidationException e) {
+ log.add(LogType.MSG_DC_ERROR_SYM_PASSPHRASE, indent +1);
+ return new DecryptVerifyResult(log,
+ RequiredInputParcel.createRequiredSymmetricPassphrase(), cryptoInput);
+ }
+
encryptedData = encryptedDataSymmetric;
symmetricEncryptionAlgo = encryptedDataSymmetric.getSymmetricAlgorithm(decryptorFactory);
@@ -548,7 +582,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
secretEncryptionKey.getRing().getMasterKeyId(),
secretEncryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
- ));
+ ),
+ cryptoInput);
}
@@ -843,6 +878,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
* The method is heavily based on
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
*/
+ @NonNull
private DecryptVerifyResult verifyCleartextSignature(
ArmoredInputStream aIn, OutputStream outputStream, int indent) throws IOException, PGPException {
@@ -950,6 +986,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
return result;
}
+ @NonNull
private DecryptVerifyResult verifyDetachedSignature(
PgpDecryptVerifyInputParcel input, InputData inputData, OutputStream out, int indent)
throws IOException, PGPException {
@@ -1042,7 +1079,9 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
return result;
}
- private PGPSignature processPGPSignatureList(PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder) throws PGPException {
+ private PGPSignature processPGPSignatureList(
+ PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder)
+ throws PGPException {
CanonicalizedPublicKeyRing signingRing = null;
CanonicalizedPublicKey signingKey = null;
int signatureIndex = -1;
@@ -1178,12 +1217,6 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
private static byte[] getLineSeparator() {
String nl = System.getProperty("line.separator");
- byte[] nlBytes = new byte[nl.length()];
-
- for (int i = 0; i != nlBytes.length; i++) {
- nlBytes[i] = (byte) nl.charAt(i);
- }
-
- return nlBytes;
+ return nl.getBytes();
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
index a018815f3..c82cbce8f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
@@ -22,6 +22,7 @@ import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.bcpg.sig.Features;
import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.bcpg.sig.RevocationReasonTags;
import org.spongycastle.jce.spec.ElGamalParameterSpec;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyFlags;
@@ -469,7 +470,7 @@ public class PgpKeyOperation {
log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent);
return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
masterSecretKey.getKeyID(), masterSecretKey.getKeyID(),
- cryptoInput.getSignatureTime()));
+ cryptoInput.getSignatureTime()), cryptoInput);
}
// read masterKeyFlags, and use the same as before.
@@ -897,18 +898,35 @@ public class PgpKeyOperation {
pKey = PGPPublicKey.removeCertification(pKey, sig);
}
- PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
- cryptoInput.getPassphrase().getCharArray());
- PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
- PGPSignature sig = generateSubkeyBindingSignature(
- getSignatureGenerator(masterSecretKey, cryptoInput),
- cryptoInput.getSignatureTime(),
- masterPublicKey, masterPrivateKey, subPrivateKey, pKey, flags, expiry);
+ PGPPrivateKey subPrivateKey;
+ if (!isDivertToCard(sKey)) {
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ cryptoInput.getPassphrase().getCharArray());
+ subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
+ // super special case: subkey is allowed to sign, but isn't available
+ if (subPrivateKey == null) {
+ log.add(LogType.MSG_MF_ERROR_SUB_STRIPPED,
+ indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+ } else {
+ subPrivateKey = null;
+ }
+ try {
+ PGPSignature sig = generateSubkeyBindingSignature(
+ getSignatureGenerator(masterSecretKey, cryptoInput),
+ cryptoInput.getSignatureTime(), masterPublicKey, masterPrivateKey,
+ getSignatureGenerator(sKey, cryptoInput), subPrivateKey,
+ pKey, flags, expiry);
+
+ // generate and add new signature
+ pKey = PGPPublicKey.addCertification(pKey, sig);
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
+ } catch (NfcInteractionNeeded e) {
+ nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
+ }
- // generate and add new signature
- pKey = PGPPublicKey.addCertification(pKey, sig);
- sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
}
subProgressPop();
@@ -959,6 +977,11 @@ public class PgpKeyOperation {
log.add(LogType.MSG_MF_SUBKEY_NEW, indent,
KeyFormattingUtils.getAlgorithmInfo(add.mAlgorithm, add.mKeySize, add.mCurve) );
+ if (isDivertToCard(masterSecretKey)) {
+ log.add(LogType.MSG_MF_ERROR_DIVERT_NEWSUB, indent +1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+
if (add.mExpiry == null) {
log.add(LogType.MSG_MF_ERROR_NULL_EXPIRY, indent +1);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
@@ -987,7 +1010,8 @@ public class PgpKeyOperation {
PGPSignature cert = generateSubkeyBindingSignature(
getSignatureGenerator(masterSecretKey, cryptoInput),
cryptoInput.getSignatureTime(),
- masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
+ masterPublicKey, masterPrivateKey,
+ getSignatureGenerator(pKey, cryptoInput, false), keyPair.getPrivateKey(), pKey,
add.mFlags, add.mExpiry);
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
} catch (NfcInteractionNeeded e) {
@@ -1039,6 +1063,26 @@ public class PgpKeyOperation {
indent -= 1;
}
+ // 7. if requested, change PIN and/or Admin PIN on card
+ if (saveParcel.mCardPin != null) {
+ progress(R.string.progress_modify_pin, 90);
+ log.add(LogType.MSG_MF_PIN, indent);
+ indent += 1;
+
+ nfcKeyToCardOps.setPin(saveParcel.mCardPin);
+
+ indent -= 1;
+ }
+ if (saveParcel.mCardAdminPin != null) {
+ progress(R.string.progress_modify_admin_pin, 90);
+ log.add(LogType.MSG_MF_ADMIN_PIN, indent);
+ indent += 1;
+
+ nfcKeyToCardOps.setAdminPin(saveParcel.mCardAdminPin);
+
+ indent -= 1;
+ }
+
} catch (IOException e) {
Log.e(Constants.TAG, "encountered IOException while modifying key", e);
log.add(LogType.MSG_MF_ERROR_ENCODE, indent+1);
@@ -1062,12 +1106,12 @@ public class PgpKeyOperation {
if (!nfcSignOps.isEmpty()) {
log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
- return new PgpEditKeyResult(log, nfcSignOps.build());
+ return new PgpEditKeyResult(log, nfcSignOps.build(), cryptoInput);
}
if (!nfcKeyToCardOps.isEmpty()) {
log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
- return new PgpEditKeyResult(log, nfcKeyToCardOps.build());
+ return new PgpEditKeyResult(log, nfcKeyToCardOps.build(), cryptoInput);
}
log.add(LogType.MSG_MF_SUCCESS, indent);
@@ -1382,22 +1426,27 @@ public class PgpKeyOperation {
static PGPSignatureGenerator getSignatureGenerator(
PGPSecretKey secretKey, CryptoInputParcel cryptoInput) {
- PGPContentSignerBuilder builder;
-
S2K s2k = secretKey.getS2K();
- if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K
- && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
+ boolean isDivertToCard = s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K
+ && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
+
+ return getSignatureGenerator(secretKey.getPublicKey(), cryptoInput, isDivertToCard);
+ }
+
+ static PGPSignatureGenerator getSignatureGenerator(
+ PGPPublicKey pKey, CryptoInputParcel cryptoInput, boolean divertToCard) {
+
+ PGPContentSignerBuilder builder;
+ if (divertToCard) {
// use synchronous "NFC based" SignerBuilder
builder = new NfcSyncPGPContentSignerBuilder(
- secretKey.getPublicKey().getAlgorithm(),
- PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO,
- secretKey.getKeyID(), cryptoInput.getCryptoData())
+ pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO,
+ pKey.getKeyID(), cryptoInput.getCryptoData())
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
} else {
// content signer based on signing key algorithm and chosen hash algorithm
builder = new JcaPGPContentSignerBuilder(
- secretKey.getPublicKey().getAlgorithm(),
- PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
+ pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
}
@@ -1477,6 +1526,9 @@ public class PgpKeyOperation {
throws IOException, PGPException, SignatureException {
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ // we use the tag NO_REASON since gnupg does not care about the tag while verifying
+ // signatures with a revoked key, the warning is the same
+ subHashedPacketsGen.setRevocationReason(true, RevocationReasonTags.NO_REASON, "");
subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey);
@@ -1489,6 +1541,9 @@ public class PgpKeyOperation {
throws IOException, PGPException, SignatureException {
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ // we use the tag NO_REASON since gnupg does not care about the tag while verifying
+ // signatures with a revoked key, the warning is the same
+ subHashedPacketsGen.setRevocationReason(true, RevocationReasonTags.NO_REASON, "");
subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
// Generate key revocation or subkey revocation, depending on master/subkey-ness
@@ -1504,7 +1559,8 @@ public class PgpKeyOperation {
static PGPSignature generateSubkeyBindingSignature(
PGPSignatureGenerator sGen, Date creationTime,
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
- PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)
+ PGPSignatureGenerator subSigGen, PGPPrivateKey subPrivateKey, PGPPublicKey pKey,
+ int flags, long expiry)
throws IOException, PGPException, SignatureException {
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
@@ -1514,10 +1570,6 @@ public class PgpKeyOperation {
// cross-certify signing keys
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, creationTime);
- PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
- pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
- PGPSignatureGenerator subSigGen = new PGPSignatureGenerator(signerBuilder);
subSigGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
subSigGen.setHashedSubpackets(subHashedPacketsGen.generate());
PGPSignature certification = subSigGen.generateCertification(masterPublicKey, pKey);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
index 89bdf1c89..8fb41a909 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
@@ -20,6 +20,8 @@
package org.sufficientlysecure.keychain.pgp;
import android.content.Context;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.BCPGOutputStream;
@@ -36,11 +38,11 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
-import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@@ -63,6 +65,7 @@ import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -99,6 +102,13 @@ public class PgpSignEncryptOperation extends BaseOperation {
super(context, providerHelper, progressable);
}
+ @NonNull
+ @Override
+ // TODO this is horrible, refactor ASAP!!
+ public OperationResult execute(Parcelable input, CryptoInputParcel cryptoInput) {
+ return null;
+ }
+
/**
* Signs and/or encrypts data based on parameters of class
*/
@@ -189,7 +199,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase(
signingKeyRing.getMasterKeyId(), signingKey.getKeyId(),
- cryptoInput.getSignatureTime()));
+ cryptoInput.getSignatureTime()), cryptoInput);
}
if (!signingKey.unlock(localPassphrase)) {
log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
@@ -263,15 +273,19 @@ public class PgpSignEncryptOperation extends BaseOperation {
try {
CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing(
KeyRings.buildUnifiedKeyRingUri(id));
- CanonicalizedPublicKey key = keyRing.getEncryptionSubKey();
- cPk.addMethod(key.getPubKeyEncryptionGenerator(input.isHiddenRecipients()));
- log.add(LogType.MSG_PSE_KEY_OK, indent + 1,
- KeyFormattingUtils.convertKeyIdToHex(id));
- } catch (PgpKeyNotFoundException e) {
- log.add(LogType.MSG_PSE_KEY_WARN, indent + 1,
- KeyFormattingUtils.convertKeyIdToHex(id));
- if (input.isFailOnMissingEncryptionKeyIds()) {
- return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ Set<Long> encryptSubKeyIds = keyRing.getEncryptIds();
+ for (Long subKeyId : encryptSubKeyIds) {
+ CanonicalizedPublicKey key = keyRing.getPublicKey(subKeyId);
+ cPk.addMethod(key.getPubKeyEncryptionGenerator(input.isHiddenRecipients()));
+ log.add(LogType.MSG_PSE_KEY_OK, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(subKeyId));
+ }
+ if (encryptSubKeyIds.isEmpty()) {
+ log.add(LogType.MSG_PSE_KEY_WARN, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(id));
+ if (input.isFailOnMissingEncryptionKeyIds()) {
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
}
} catch (ProviderHelper.NotFoundException e) {
log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1,
@@ -498,7 +512,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
log.add(LogType.MSG_PSE_PENDING_NFC, indent);
return new PgpSignEncryptResult(log, RequiredInputParcel.createNfcSignOperation(
signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(),
- e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()));
+ e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()), cryptoInput);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
index 7be61d9c8..a7baddf8b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
@@ -48,6 +48,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -78,7 +79,7 @@ import java.util.TreeSet;
*
*/
@SuppressWarnings("unchecked")
-public class UncachedKeyRing {
+public class UncachedKeyRing implements Serializable {
final PGPKeyRing mRing;
final boolean mIsSecret;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
index 3346926ec..4d16d44c5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -53,7 +53,7 @@ import java.io.IOException;
*/
public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db";
- private static final int DATABASE_VERSION = 10;
+ private static final int DATABASE_VERSION = 11;
static Boolean apgHack = false;
private Context mContext;
@@ -274,6 +274,14 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_CERTS);
case 10:
// do nothing here, just consolidate
+ case 11:
+ // fix problems in database, see #1402 for details
+ // https://github.com/open-keychain/open-keychain/issues/1402
+ db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
+ if (oldVersion == 10) {
+ // no consolidate if we are updating from 10, we're just here for the api_accounts fix
+ return;
+ }
}
@@ -297,10 +305,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
// It's the Java way =(
String[] dbs = context.databaseList();
for (String db : dbs) {
- if (db.equals("apg.db")) {
+ if ("apg.db".equals(db)) {
hasApgDb = true;
- } else if (db.equals("apg_old.db")) {
+ } else if ("apg_old.db".equals(db)) {
Log.d(Constants.TAG, "Found apg_old.db, delete it!");
+ // noinspection ResultOfMethodCallIgnored - if it doesn't happen, it doesn't happen.
context.getDatabasePath("apg_old.db").delete();
}
}
@@ -384,7 +393,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
}
}
- // delete old database
+ // noinspection ResultOfMethodCallIgnored - not much we can do if this doesn't work
context.getDatabasePath("apg.db").delete();
}
@@ -416,6 +425,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
} else {
in = context.getDatabasePath(DATABASE_NAME);
out = context.getDatabasePath("debug_backup.db");
+ // noinspection ResultOfMethodCallIgnored - this is a pure debug feature, anyways
out.createNewFile();
}
if (!in.canRead()) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
index 590c58f97..ed17de4ab 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -26,6 +26,7 @@ import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
+import android.support.annotation.NonNull;
import android.support.v4.util.LongSparseArray;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
@@ -725,7 +726,7 @@ public class ProviderHelper {
LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<>();
@Override
- public int compareTo(UserPacketItem o) {
+ public int compareTo(@NonNull UserPacketItem o) {
// revoked keys always come last!
//noinspection DoubleNegation
if ((selfRevocation != null) != (o.selfRevocation != null)) {
@@ -906,7 +907,8 @@ public class ProviderHelper {
// If there is a secret key, merge new data (if any) and save the key for later
CanonicalizedSecretKeyRing canSecretRing;
try {
- UncachedKeyRing secretRing = getCanonicalizedSecretKeyRing(publicRing.getMasterKeyId()).getUncachedKeyRing();
+ UncachedKeyRing secretRing = getCanonicalizedSecretKeyRing(publicRing.getMasterKeyId())
+ .getUncachedKeyRing();
// Merge data from new public ring into secret one
log(LogType.MSG_IP_MERGE_SECRET);
@@ -1031,7 +1033,8 @@ public class ProviderHelper {
publicRing = secretRing.extractPublicKeyRing();
}
- CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
+ CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog,
+ mIndent);
if (canPublicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
@@ -1057,6 +1060,7 @@ public class ProviderHelper {
}
+ @NonNull
public ConsolidateResult consolidateDatabaseStep1(Progressable progress) {
OperationLog log = new OperationLog();
@@ -1082,7 +1086,7 @@ public class ProviderHelper {
indent += 1;
final Cursor cursor = mContentResolver.query(KeyRingData.buildSecretKeyRingUri(),
- new String[]{ KeyRingData.KEY_RING_DATA }, null, null, null);
+ new String[]{KeyRingData.KEY_RING_DATA}, null, null, null);
if (cursor == null) {
log.add(LogType.MSG_CON_ERROR_DB, indent);
@@ -1124,6 +1128,7 @@ public class ProviderHelper {
}
});
+ cursor.close();
} catch (IOException e) {
Log.e(Constants.TAG, "error saving secret", e);
@@ -1143,7 +1148,7 @@ public class ProviderHelper {
final Cursor cursor = mContentResolver.query(
KeyRingData.buildPublicKeyRingUri(),
- new String[]{ KeyRingData.KEY_RING_DATA }, null, null, null);
+ new String[]{KeyRingData.KEY_RING_DATA}, null, null, null);
if (cursor == null) {
log.add(LogType.MSG_CON_ERROR_DB, indent);
@@ -1185,6 +1190,7 @@ public class ProviderHelper {
}
});
+ cursor.close();
} catch (IOException e) {
Log.e(Constants.TAG, "error saving public", e);
@@ -1200,12 +1206,14 @@ public class ProviderHelper {
return consolidateDatabaseStep2(log, indent, progress, false);
}
+ @NonNull
public ConsolidateResult consolidateDatabaseStep2(Progressable progress) {
return consolidateDatabaseStep2(new OperationLog(), 0, progress, true);
}
private static boolean mConsolidateCritical = false;
+ @NonNull
private ConsolidateResult consolidateDatabaseStep2(
OperationLog log, int indent, Progressable progress, boolean recovery) {
@@ -1250,7 +1258,7 @@ public class ProviderHelper {
ImportKeyResult result = new ImportOperation(mContext, this,
new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport))
- .serialKeyRingImport(itSecrets, numSecrets, null);
+ .serialKeyRingImport(itSecrets, numSecrets, null, null);
log.add(result, indent);
} else {
log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent);
@@ -1278,7 +1286,7 @@ public class ProviderHelper {
ImportKeyResult result = new ImportOperation(mContext, this,
new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport))
- .serialKeyRingImport(itPublics, numPublics, null);
+ .serialKeyRingImport(itPublics, numPublics, null, null);
log.add(result, indent);
} else {
log.add(LogType.MSG_CON_REIMPORT_PUBLIC_SKIP, indent);
@@ -1414,7 +1422,7 @@ public class ProviderHelper {
private ContentValues contentValueForApiApps(AppSettings appSettings) {
ContentValues values = new ContentValues();
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
- values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageSignature());
+ values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate());
return values;
}
@@ -1460,7 +1468,7 @@ public class ProviderHelper {
settings = new AppSettings();
settings.setPackageName(cursor.getString(
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
- settings.setPackageSignature(cursor.getBlob(
+ settings.setPackageCertificate(cursor.getBlob(
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_CERTIFICATE)));
}
} finally {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java
index a3f9f84c9..498601769 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java
@@ -19,7 +19,7 @@ package org.sufficientlysecure.keychain.remote;
public class AppSettings {
private String mPackageName;
- private byte[] mPackageSignature;
+ private byte[] mPackageCertificate;
public AppSettings() {
@@ -28,7 +28,7 @@ public class AppSettings {
public AppSettings(String packageName, byte[] packageSignature) {
super();
this.mPackageName = packageName;
- this.mPackageSignature = packageSignature;
+ this.mPackageCertificate = packageSignature;
}
public String getPackageName() {
@@ -39,12 +39,12 @@ public class AppSettings {
this.mPackageName = packageName;
}
- public byte[] getPackageSignature() {
- return mPackageSignature;
+ public byte[] getPackageCertificate() {
+ return mPackageCertificate;
}
- public void setPackageSignature(byte[] packageSignature) {
- this.mPackageSignature = packageSignature;
+ public void setPackageCertificate(byte[] packageCertificate) {
+ this.mPackageCertificate = packageCertificate;
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
index ac66bd097..2568d68b9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
@@ -64,6 +64,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Date;
import java.util.HashSet;
public class OpenPgpService extends RemoteService {
@@ -88,8 +89,8 @@ public class OpenPgpService extends RemoteService {
boolean duplicateUserIdsCheck = false;
ArrayList<Long> keyIds = new ArrayList<>();
- ArrayList<String> missingUserIds = new ArrayList<>();
- ArrayList<String> duplicateUserIds = new ArrayList<>();
+ ArrayList<String> missingEmails = new ArrayList<>();
+ ArrayList<String> duplicateEmails = new ArrayList<>();
if (!noUserIdsCheck) {
for (String email : encryptionUserIds) {
// try to find the key for this specific email
@@ -102,13 +103,13 @@ public class OpenPgpService extends RemoteService {
keyIds.add(id);
} else {
missingUserIdsCheck = true;
- missingUserIds.add(email);
+ missingEmails.add(email);
Log.d(Constants.TAG, "user id missing");
}
- // another entry for this email -> too keys with the same email inside user id
+ // another entry for this email -> two keys with the same email inside user id
if (cursor != null && cursor.moveToNext()) {
duplicateUserIdsCheck = true;
- duplicateUserIds.add(email);
+ duplicateEmails.add(email);
// also pre-select
long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID));
@@ -136,8 +137,8 @@ public class OpenPgpService extends RemoteService {
intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS);
intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
intent.putExtra(RemoteServiceActivity.EXTRA_NO_USER_IDS_CHECK, noUserIdsCheck);
- intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
- intent.putExtra(RemoteServiceActivity.EXTRA_DUPLICATE_USER_IDS, duplicateUserIds);
+ intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_EMAILS, missingEmails);
+ intent.putExtra(RemoteServiceActivity.EXTRA_DUPLICATE_EMAILS, duplicateEmails);
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
@@ -164,7 +165,9 @@ public class OpenPgpService extends RemoteService {
}
private static PendingIntent getRequiredInputPendingIntent(Context context,
- Intent data, RequiredInputParcel requiredInput) {
+ Intent data,
+ RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInput) {
switch (requiredInput.mType) {
case NFC_MOVE_KEY_TO_CARD:
@@ -175,6 +178,7 @@ public class OpenPgpService extends RemoteService {
// pass params through to activity that it can be returned again later to repeat pgp operation
intent.putExtra(NfcOperationActivity.EXTRA_SERVICE_INTENT, data);
intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
+ intent.putExtra(NfcOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInput);
return PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
}
@@ -185,6 +189,7 @@ public class OpenPgpService extends RemoteService {
// pass params through to activity that it can be returned again later to repeat pgp operation
intent.putExtra(PassphraseDialogActivity.EXTRA_SERVICE_INTENT, data);
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInput);
return PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
}
@@ -279,12 +284,12 @@ public class OpenPgpService extends RemoteService {
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
if (inputParcel == null) {
- inputParcel = new CryptoInputParcel();
+ inputParcel = new CryptoInputParcel(new Date());
}
// override passphrase in input parcel if given by API call
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
- inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
- new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
+ inputParcel.mPassphrase =
+ new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
}
// execute PGP operation!
@@ -294,7 +299,8 @@ public class OpenPgpService extends RemoteService {
if (pgpResult.isPending()) {
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
- PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
+ PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data,
+ requiredInput, pgpResult.mCryptoInputParcel);
// return PendingIntent to be executed by client
Intent result = new Intent();
@@ -434,12 +440,12 @@ public class OpenPgpService extends RemoteService {
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
if (inputParcel == null) {
- inputParcel = new CryptoInputParcel();
+ inputParcel = new CryptoInputParcel(new Date());
}
// override passphrase in input parcel if given by API call
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
- inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
- new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
+ inputParcel.mPassphrase =
+ new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
}
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
@@ -449,7 +455,8 @@ public class OpenPgpService extends RemoteService {
if (pgpResult.isPending()) {
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
- PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
+ PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data,
+ requiredInput, pgpResult.mCryptoInputParcel);
// return PendingIntent to be executed by client
Intent result = new Intent();
@@ -519,8 +526,8 @@ public class OpenPgpService extends RemoteService {
}
// override passphrase in input parcel if given by API call
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
- cryptoInput = new CryptoInputParcel(cryptoInput.getSignatureTime(),
- new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
+ cryptoInput.mPassphrase =
+ new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
}
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
@@ -543,7 +550,8 @@ public class OpenPgpService extends RemoteService {
if (pgpResult.isPending()) {
// prepare and return PendingIntent to be executed by client
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
- PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
+ PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data,
+ requiredInput, pgpResult.mCryptoInputParcel);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
index d25249b14..4eee73e01 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
@@ -75,7 +75,7 @@ public class AppSettingsActivity extends BaseActivity {
mAppNameView = (TextView) findViewById(R.id.api_app_settings_app_name);
mAppIconView = (ImageView) findViewById(R.id.api_app_settings_app_icon);
mPackageName = (TextView) findViewById(R.id.api_app_settings_package_name);
- mPackageSignature = (TextView) findViewById(R.id.api_app_settings_package_signature);
+ mPackageSignature = (TextView) findViewById(R.id.api_app_settings_package_certificate);
mStartFab = (FloatingActionButton) findViewById(R.id.fab);
mStartFab.setOnClickListener(new View.OnClickListener() {
@@ -148,19 +148,19 @@ public class AppSettingsActivity extends BaseActivity {
}
private void showAdvancedInfo() {
- String signature = null;
- // advanced info: package signature SHA-256
+ String certificate = null;
+ // advanced info: package certificate SHA-256
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
- md.update(mAppSettings.getPackageSignature());
+ md.update(mAppSettings.getPackageCertificate());
byte[] digest = md.digest();
- signature = new String(Hex.encode(digest));
+ certificate = new String(Hex.encode(digest));
} catch (NoSuchAlgorithmException e) {
Log.e(Constants.TAG, "Should not happen!", e);
}
AdvancedAppSettingsDialogFragment dialogFragment =
- AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), signature);
+ AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), certificate);
dialogFragment.show(getSupportFragmentManager(), "advancedDialog");
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java
index 7beac8973..9160987ab 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java
@@ -47,7 +47,7 @@ public class AppSettingsHeaderFragment extends Fragment {
private TextView mAppNameView;
private ImageView mAppIconView;
private TextView mPackageName;
- private TextView mPackageSignature;
+ private TextView mPackageCertificate;
public AppSettings getAppSettings() {
return mAppSettings;
@@ -67,7 +67,7 @@ public class AppSettingsHeaderFragment extends Fragment {
mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name);
mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon);
mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name);
- mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature);
+ mPackageCertificate = (TextView) view.findViewById(R.id.api_app_settings_package_certificate);
return view;
}
@@ -94,11 +94,11 @@ public class AppSettingsHeaderFragment extends Fragment {
// advanced info: package signature SHA-256
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
- md.update(appSettings.getPackageSignature());
+ md.update(appSettings.getPackageCertificate());
byte[] digest = md.digest();
String signature = new String(Hex.encode(digest));
- mPackageSignature.setText(signature);
+ mPackageCertificate.setText(signature);
} catch (NoSuchAlgorithmException e) {
Log.e(Constants.TAG, "Should not happen!", e);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
index 5facde64f..a2e781e8a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
@@ -69,8 +69,8 @@ public class RemoteServiceActivity extends BaseActivity {
public static final String EXTRA_ACC_NAME = "acc_name";
// select pub keys action
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
- public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids";
- public static final String EXTRA_DUPLICATE_USER_IDS = "dublicate_user_ids";
+ public static final String EXTRA_MISSING_EMAILS = "missing_emails";
+ public static final String EXTRA_DUPLICATE_EMAILS = "dublicate_emails";
public static final String EXTRA_NO_USER_IDS_CHECK = "no_user_ids";
// error message
public static final String EXTRA_ERROR_MESSAGE = "error_message";
@@ -222,10 +222,10 @@ public class RemoteServiceActivity extends BaseActivity {
case ACTION_SELECT_PUB_KEYS: {
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
boolean noUserIdsCheck = intent.getBooleanExtra(EXTRA_NO_USER_IDS_CHECK, true);
- ArrayList<String> missingUserIds = intent
- .getStringArrayListExtra(EXTRA_MISSING_USER_IDS);
- ArrayList<String> dublicateUserIds = intent
- .getStringArrayListExtra(EXTRA_DUPLICATE_USER_IDS);
+ ArrayList<String> missingEmails = intent
+ .getStringArrayListExtra(EXTRA_MISSING_EMAILS);
+ ArrayList<String> duplicateEmails = intent
+ .getStringArrayListExtra(EXTRA_DUPLICATE_EMAILS);
SpannableStringBuilder ssb = new SpannableStringBuilder();
final SpannableString textIntro = new SpannableString(
@@ -235,23 +235,23 @@ public class RemoteServiceActivity extends BaseActivity {
textIntro.setSpan(new StyleSpan(Typeface.BOLD), 0, textIntro.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.append(textIntro);
- if (missingUserIds != null && missingUserIds.size() > 0) {
+ if (missingEmails != null && missingEmails.size() > 0) {
ssb.append("\n\n");
ssb.append(getString(R.string.api_select_pub_keys_missing_text));
ssb.append("\n");
- for (String userId : missingUserIds) {
- SpannableString ss = new SpannableString(userId + "\n");
+ for (String emails : missingEmails) {
+ SpannableString ss = new SpannableString(emails + "\n");
ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.append(ss);
}
}
- if (dublicateUserIds != null && dublicateUserIds.size() > 0) {
+ if (duplicateEmails != null && duplicateEmails.size() > 0) {
ssb.append("\n\n");
ssb.append(getString(R.string.api_select_pub_keys_dublicates_text));
ssb.append("\n");
- for (String userId : dublicateUserIds) {
- SpannableString ss = new SpannableString(userId + "\n");
+ for (String email : duplicateEmails) {
+ SpannableString ss = new SpannableString(email + "\n");
ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.append(ss);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java
index a11f81658..e76dcfb49 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java
@@ -22,13 +22,10 @@ import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
-import java.util.Date;
-import java.util.Map;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
-import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.util.ParcelableProxy;
/**
@@ -54,6 +51,7 @@ public class CertifyActionsParcel implements Parcelable {
mMasterKeyId = source.readLong();
// just like parcelables, this is meant for ad-hoc IPC only and is NOT portable!
mLevel = CertifyLevel.values()[source.readInt()];
+ keyServerUri = source.readString();
mCertifyActions = (ArrayList<CertifyAction>) source.readSerializable();
}
@@ -66,6 +64,7 @@ public class CertifyActionsParcel implements Parcelable {
public void writeToParcel(Parcel destination, int flags) {
destination.writeLong(mMasterKeyId);
destination.writeInt(mLevel.ordinal());
+ destination.writeString(keyServerUri);
destination.writeSerializable(mCertifyActions);
}
@@ -94,7 +93,7 @@ public class CertifyActionsParcel implements Parcelable {
}
public CertifyAction(long masterKeyId, ArrayList<String> userIds,
- ArrayList<WrappedUserAttribute> attributes) {
+ ArrayList<WrappedUserAttribute> attributes) {
mMasterKeyId = masterKeyId;
mUserIds = userIds;
mUserAttributes = attributes;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java
index 41ff6d02b..18bc3cc9d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java
@@ -83,7 +83,7 @@ public class DummyAccountService extends Service {
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType,
String[] requiredFeatures, Bundle options) throws NetworkErrorException {
response.onResult(new Bundle());
- toaster.toast(R.string.info_no_manual_account_creation);
+ toaster.toast(R.string.account_no_manual_account_creation);
Log.d(Constants.TAG, "DummyAccountService.addAccount");
return null;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java
index ef5b48df3..24c002bbd 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java
@@ -23,9 +23,12 @@ import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+
public class ExportKeyringParcel implements Parcelable {
public String mKeyserver;
public Uri mCanonicalizedPublicKeyringUri;
+ public UncachedKeyRing mUncachedKeyRing;
public boolean mExportSecret;
public long mMasterKeyIds[];
@@ -45,6 +48,12 @@ public class ExportKeyringParcel implements Parcelable {
mCanonicalizedPublicKeyringUri = keyringUri;
}
+ public ExportKeyringParcel(String keyserver, UncachedKeyRing uncachedKeyRing) {
+ mExportType = ExportType.UPLOAD_KEYSERVER;
+ mKeyserver = keyserver;
+ mUncachedKeyRing = uncachedKeyRing;
+ }
+
public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, String outputFile) {
mExportType = ExportType.EXPORT_FILE;
mMasterKeyIds = masterKeyIds;
@@ -52,6 +61,7 @@ public class ExportKeyringParcel implements Parcelable {
mOutputFile = outputFile;
}
+ @SuppressWarnings("unused") // TODO: is it used?
public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, Uri outputUri) {
mExportType = ExportType.EXPORT_URI;
mMasterKeyIds = masterKeyIds;
@@ -62,6 +72,7 @@ public class ExportKeyringParcel implements Parcelable {
protected ExportKeyringParcel(Parcel in) {
mKeyserver = in.readString();
mCanonicalizedPublicKeyringUri = (Uri) in.readValue(Uri.class.getClassLoader());
+ mUncachedKeyRing = (UncachedKeyRing) in.readValue(UncachedKeyRing.class.getClassLoader());
mExportSecret = in.readByte() != 0x00;
mOutputFile = in.readString();
mOutputUri = (Uri) in.readValue(Uri.class.getClassLoader());
@@ -78,6 +89,7 @@ public class ExportKeyringParcel implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mKeyserver);
dest.writeValue(mCanonicalizedPublicKeyringUri);
+ dest.writeValue(mUncachedKeyRing);
dest.writeByte((byte) (mExportSecret ? 0x01 : 0x00));
dest.writeString(mOutputFile);
dest.writeValue(mOutputUri);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
index 1cd76b462..dca2a08c2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
@@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.operations.ExportOperation;
import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation;
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
+import org.sufficientlysecure.keychain.operations.RevokeOperation;
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
@@ -114,6 +115,8 @@ public class KeychainService extends Service implements Progressable {
} else if (inputParcel instanceof SaveKeyringParcel) {
op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
mActionCanceled);
+ } else if (inputParcel instanceof RevokeKeyringParcel) {
+ op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof CertifyActionsParcel) {
op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
mActionCanceled);
@@ -135,7 +138,7 @@ public class KeychainService extends Service implements Progressable {
op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis),
outerThis);
} else {
- return;
+ throw new AssertionError("Unrecognized input parcel in KeychainService!");
}
@SuppressWarnings("unchecked") // this is unchecked, we make sure it's the correct op above!
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
index 7c0b7eaef..2a258b7e3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -232,21 +232,21 @@ public class PassphraseCacheService extends Service {
* Internal implementation to get cached passphrase.
*/
private Passphrase getCachedPassphraseImpl(long masterKeyId, long subKeyId) throws ProviderHelper.NotFoundException {
+ // on "none" key, just do nothing
+ if (masterKeyId == Constants.key.none) {
+ return null;
+ }
+
// passphrase for symmetric encryption?
if (masterKeyId == Constants.key.symmetric) {
Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for symmetric encryption");
- Passphrase cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric).getPassphrase();
+ CachedPassphrase cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric);
if (cachedPassphrase == null) {
return null;
}
addCachedPassphrase(this, Constants.key.symmetric, Constants.key.symmetric,
- cachedPassphrase, getString(R.string.passp_cache_notif_pwd));
- return cachedPassphrase;
- }
-
- // on "none" key, just do nothing
- if (masterKeyId == Constants.key.none) {
- return null;
+ cachedPassphrase.getPassphrase(), getString(R.string.passp_cache_notif_pwd));
+ return cachedPassphrase.getPassphrase();
}
// try to get master key id which is used as an identifier for cached passphrases
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/RevokeKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/RevokeKeyringParcel.java
new file mode 100644
index 000000000..02e8fda46
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/RevokeKeyringParcel.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.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.service;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class RevokeKeyringParcel implements Parcelable {
+
+ final public long mMasterKeyId;
+ final public boolean mUpload;
+ final public String mKeyserver;
+
+ public RevokeKeyringParcel(long masterKeyId, boolean upload, String keyserver) {
+ mMasterKeyId = masterKeyId;
+ mUpload = upload;
+ mKeyserver = keyserver;
+ }
+
+ protected RevokeKeyringParcel(Parcel in) {
+ mMasterKeyId = in.readLong();
+ mUpload = in.readByte() != 0x00;
+ mKeyserver = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mMasterKeyId);
+ dest.writeByte((byte) (mUpload ? 0x01 : 0x00));
+ dest.writeString(mKeyserver);
+ }
+
+ public static final Parcelable.Creator<RevokeKeyringParcel> CREATOR = new Parcelable.Creator<RevokeKeyringParcel>() {
+ @Override
+ public RevokeKeyringParcel createFromParcel(Parcel in) {
+ return new RevokeKeyringParcel(in);
+ }
+
+ @Override
+ public RevokeKeyringParcel[] newArray(int size) {
+ return new RevokeKeyringParcel[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
index e2c4dc542..6959fca56 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
@@ -61,6 +61,15 @@ public class SaveKeyringParcel implements Parcelable {
public ArrayList<String> mRevokeUserIds;
public ArrayList<Long> mRevokeSubKeys;
+ // if these are non-null, PINs will be changed on the card
+ public Passphrase mCardPin;
+ public Passphrase mCardAdminPin;
+
+ // private because they have to be set together with setUpdateOptions
+ private boolean mUpload;
+ private boolean mUploadAtomic;
+ private String mKeyserver;
+
public SaveKeyringParcel() {
reset();
}
@@ -80,6 +89,29 @@ public class SaveKeyringParcel implements Parcelable {
mChangeSubKeys = new ArrayList<>();
mRevokeUserIds = new ArrayList<>();
mRevokeSubKeys = new ArrayList<>();
+ mCardPin = null;
+ mCardAdminPin = null;
+ mUpload = false;
+ mUploadAtomic = false;
+ mKeyserver = null;
+ }
+
+ public void setUpdateOptions(boolean upload, boolean uploadAtomic, String keysever) {
+ mUpload = upload;
+ mUploadAtomic = uploadAtomic;
+ mKeyserver = keysever;
+ }
+
+ public boolean isUpload() {
+ return mUpload;
+ }
+
+ public boolean isUploadAtomic() {
+ return mUploadAtomic;
+ }
+
+ public String getUploadKeyserver() {
+ return mKeyserver;
}
public boolean isEmpty() {
@@ -210,6 +242,7 @@ public class SaveKeyringParcel implements Parcelable {
}
}
+ @SuppressWarnings("unchecked") // we verify the reads against writes in writeToParcel
public SaveKeyringParcel(Parcel source) {
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
mFingerprint = source.createByteArray();
@@ -225,6 +258,13 @@ public class SaveKeyringParcel implements Parcelable {
mRevokeUserIds = source.createStringArrayList();
mRevokeSubKeys = (ArrayList<Long>) source.readSerializable();
+
+ mCardPin = source.readParcelable(Passphrase.class.getClassLoader());
+ mCardAdminPin = source.readParcelable(Passphrase.class.getClassLoader());
+
+ mUpload = source.readByte() != 0;
+ mUploadAtomic = source.readByte() != 0;
+ mKeyserver = source.readString();
}
@Override
@@ -236,7 +276,7 @@ public class SaveKeyringParcel implements Parcelable {
destination.writeByteArray(mFingerprint);
// yes, null values are ok for parcelables
- destination.writeParcelable(mNewUnlock, 0);
+ destination.writeParcelable(mNewUnlock, flags);
destination.writeStringList(mAddUserIds);
destination.writeSerializable(mAddUserAttribute);
@@ -247,6 +287,13 @@ public class SaveKeyringParcel implements Parcelable {
destination.writeStringList(mRevokeUserIds);
destination.writeSerializable(mRevokeSubKeys);
+
+ destination.writeParcelable(mCardPin, flags);
+ destination.writeParcelable(mCardAdminPin, flags);
+
+ destination.writeByte((byte) (mUpload ? 1 : 0));
+ destination.writeByte((byte) (mUploadAtomic ? 1 : 0));
+ destination.writeString(mKeyserver);
}
public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() {
@@ -274,7 +321,9 @@ public class SaveKeyringParcel implements Parcelable {
out += "mChangeSubKeys: " + mChangeSubKeys + "\n";
out += "mChangePrimaryUserId: " + mChangePrimaryUserId + "\n";
out += "mRevokeUserIds: " + mRevokeUserIds + "\n";
- out += "mRevokeSubKeys: " + mRevokeSubKeys;
+ out += "mRevokeSubKeys: " + mRevokeSubKeys + "\n";
+ out += "mCardPin: " + mCardPin + "\n";
+ out += "mCardAdminPin: " + mCardAdminPin;
return out;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
index ee7caf2d8..0d8569fe6 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
@@ -17,52 +17,87 @@
package org.sufficientlysecure.keychain.service.input;
-import java.nio.ByteBuffer;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
import android.os.Parcel;
import android.os.Parcelable;
+import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Passphrase;
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* This is a base class for the input of crypto operations.
*/
public class CryptoInputParcel implements Parcelable {
- final Date mSignatureTime;
- final Passphrase mPassphrase;
+ private Date mSignatureTime;
+ private boolean mHasSignature;
+
+ public Passphrase mPassphrase;
+ // used to supply an explicit proxy to operations that require it
+ // this is not final so it can be added to an existing CryptoInputParcel
+ // (e.g) CertifyOperation with upload might require both passphrase and orbot to be enabled
+ private ParcelableProxy mParcelableProxy;
+
+ // specifies whether passphrases should be cached
+ public boolean mCachePassphrase = true;
// this map contains both decrypted session keys and signed hashes to be
// used in the crypto operation described by this parcel.
private HashMap<ByteBuffer, byte[]> mCryptoData = new HashMap<>();
public CryptoInputParcel() {
- mSignatureTime = new Date();
+ mSignatureTime = null;
mPassphrase = null;
+ mCachePassphrase = true;
}
public CryptoInputParcel(Date signatureTime, Passphrase passphrase) {
+ mHasSignature = true;
mSignatureTime = signatureTime == null ? new Date() : signatureTime;
mPassphrase = passphrase;
+ mCachePassphrase = true;
}
public CryptoInputParcel(Passphrase passphrase) {
- mSignatureTime = new Date();
mPassphrase = passphrase;
+ mCachePassphrase = true;
}
public CryptoInputParcel(Date signatureTime) {
+ mHasSignature = true;
+ mSignatureTime = signatureTime == null ? new Date() : signatureTime;
+ mPassphrase = null;
+ mCachePassphrase = true;
+ }
+
+ public CryptoInputParcel(ParcelableProxy parcelableProxy) {
+ this();
+ mParcelableProxy = parcelableProxy;
+ }
+
+ public CryptoInputParcel(Date signatureTime, boolean cachePassphrase) {
+ mHasSignature = true;
mSignatureTime = signatureTime == null ? new Date() : signatureTime;
mPassphrase = null;
+ mCachePassphrase = cachePassphrase;
+ }
+
+ public CryptoInputParcel(boolean cachePassphrase) {
+ mCachePassphrase = cachePassphrase;
}
protected CryptoInputParcel(Parcel source) {
- mSignatureTime = new Date(source.readLong());
+ mHasSignature = source.readByte() != 0;
+ if (mHasSignature) {
+ mSignatureTime = new Date(source.readLong());
+ }
mPassphrase = source.readParcelable(getClass().getClassLoader());
+ mParcelableProxy = source.readParcelable(getClass().getClassLoader());
+ mCachePassphrase = source.readByte() != 0;
{
int count = source.readInt();
@@ -83,8 +118,13 @@ public class CryptoInputParcel implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeLong(mSignatureTime.getTime());
+ dest.writeByte((byte) (mHasSignature ? 1 : 0));
+ if (mHasSignature) {
+ dest.writeLong(mSignatureTime.getTime());
+ }
dest.writeParcelable(mPassphrase, 0);
+ dest.writeParcelable(mParcelableProxy, 0);
+ dest.writeByte((byte) (mCachePassphrase ? 1 : 0));
dest.writeInt(mCryptoData.size());
for (HashMap.Entry<ByteBuffer, byte[]> entry : mCryptoData.entrySet()) {
@@ -93,6 +133,14 @@ public class CryptoInputParcel implements Parcelable {
}
}
+ public void addParcelableProxy(ParcelableProxy parcelableProxy) {
+ mParcelableProxy = parcelableProxy;
+ }
+
+ public void addSignatureTime(Date signatureTime) {
+ mSignatureTime = signatureTime;
+ }
+
public void addCryptoData(byte[] hash, byte[] signedHash) {
mCryptoData.put(ByteBuffer.wrap(hash), signedHash);
}
@@ -101,6 +149,10 @@ public class CryptoInputParcel implements Parcelable {
mCryptoData.putAll(cachedSessionKeys);
}
+ public ParcelableProxy getParcelableProxy() {
+ return mParcelableProxy;
+ }
+
public Map<ByteBuffer, byte[]> getCryptoData() {
return mCryptoData;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java
index efe844145..e4dac3227 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java
@@ -1,34 +1,37 @@
package org.sufficientlysecure.keychain.service.input;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.sufficientlysecure.keychain.util.Passphrase;
+
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
-import android.os.Parcel;
-import android.os.Parcelable;
-
public class RequiredInputParcel implements Parcelable {
public enum RequiredInputType {
- PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD
+ PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD, ENABLE_ORBOT,
+ UPLOAD_FAIL_RETRY
}
public Date mSignatureTime;
public final RequiredInputType mType;
- public final byte[][] mInputHashes;
+ public final byte[][] mInputData;
public final int[] mSignAlgos;
private Long mMasterKeyId;
private Long mSubKeyId;
- private RequiredInputParcel(RequiredInputType type, byte[][] inputHashes,
+ private RequiredInputParcel(RequiredInputType type, byte[][] inputData,
int[] signAlgos, Date signatureTime, Long masterKeyId, Long subKeyId) {
mType = type;
- mInputHashes = inputHashes;
+ mInputData = inputData;
mSignAlgos = signAlgos;
mSignatureTime = signatureTime;
mMasterKeyId = masterKeyId;
@@ -38,25 +41,25 @@ public class RequiredInputParcel implements Parcelable {
public RequiredInputParcel(Parcel source) {
mType = RequiredInputType.values()[source.readInt()];
- // 0 = none, 1 = both, 2 = only hashes (decrypt)
- int hashTypes = source.readInt();
- if (hashTypes != 0) {
+ // 0 = none, 1 = signAlgos + inputData, 2 = only inputData (decrypt)
+ int inputDataType = source.readInt();
+ if (inputDataType != 0) {
int count = source.readInt();
- mInputHashes = new byte[count][];
- if (hashTypes == 1) {
+ mInputData = new byte[count][];
+ if (inputDataType == 1) {
mSignAlgos = new int[count];
for (int i = 0; i < count; i++) {
- mInputHashes[i] = source.createByteArray();
+ mInputData[i] = source.createByteArray();
mSignAlgos[i] = source.readInt();
}
} else {
mSignAlgos = null;
for (int i = 0; i < count; i++) {
- mInputHashes[i] = source.createByteArray();
+ mInputData[i] = source.createByteArray();
}
}
} else {
- mInputHashes = null;
+ mInputData = null;
mSignAlgos = null;
}
@@ -74,6 +77,15 @@ public class RequiredInputParcel implements Parcelable {
return mSubKeyId;
}
+ public static RequiredInputParcel createRetryUploadOperation() {
+ return new RequiredInputParcel(RequiredInputType.UPLOAD_FAIL_RETRY,
+ null, null, null, 0L, 0L);
+ }
+
+ public static RequiredInputParcel createOrbotRequiredOperation() {
+ return new RequiredInputParcel(RequiredInputType.ENABLE_ORBOT, null, null, null, 0L, 0L);
+ }
+
public static RequiredInputParcel createNfcSignOperation(
long masterKeyId, long subKeyId,
byte[] inputHash, int signAlgo, Date signatureTime) {
@@ -83,9 +95,9 @@ public class RequiredInputParcel implements Parcelable {
}
public static RequiredInputParcel createNfcDecryptOperation(
- long masterKeyId, long subKeyId, byte[] inputHash) {
+ long masterKeyId, long subKeyId, byte[] encryptedSessionKey) {
return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT,
- new byte[][] { inputHash }, null, null, masterKeyId, subKeyId);
+ new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId);
}
public static RequiredInputParcel createRequiredSignPassphrase(
@@ -119,11 +131,11 @@ public class RequiredInputParcel implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType.ordinal());
- if (mInputHashes != null) {
+ if (mInputData != null) {
dest.writeInt(mSignAlgos != null ? 1 : 2);
- dest.writeInt(mInputHashes.length);
- for (int i = 0; i < mInputHashes.length; i++) {
- dest.writeByteArray(mInputHashes[i]);
+ dest.writeInt(mInputData.length);
+ for (int i = 0; i < mInputData.length; i++) {
+ dest.writeByteArray(mInputData[i]);
if (mSignAlgos != null) {
dest.writeInt(mSignAlgos[i]);
}
@@ -200,7 +212,7 @@ public class RequiredInputParcel implements Parcelable {
throw new AssertionError("operation types must match, this is a progrmming error!");
}
- Collections.addAll(mInputHashes, input.mInputHashes);
+ Collections.addAll(mInputHashes, input.mInputData);
for (int signAlgo : input.mSignAlgos) {
mSignAlgos.add(signAlgo);
}
@@ -215,19 +227,31 @@ public class RequiredInputParcel implements Parcelable {
public static class NfcKeyToCardOperationsBuilder {
ArrayList<byte[]> mSubkeysToExport = new ArrayList<>();
Long mMasterKeyId;
+ byte[] mPin;
+ byte[] mAdminPin;
public NfcKeyToCardOperationsBuilder(Long masterKeyId) {
mMasterKeyId = masterKeyId;
}
public RequiredInputParcel build() {
- byte[][] inputHashes = new byte[mSubkeysToExport.size()][];
- mSubkeysToExport.toArray(inputHashes);
+ byte[][] inputData = new byte[mSubkeysToExport.size() + 2][];
+
+ // encode all subkeys into inputData
+ byte[][] subkeyData = new byte[mSubkeysToExport.size()][];
+ mSubkeysToExport.toArray(subkeyData);
+
+ // first two are PINs
+ inputData[0] = mPin;
+ inputData[1] = mAdminPin;
+ // then subkeys
+ System.arraycopy(subkeyData, 0, inputData, 2, subkeyData.length);
+
ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0));
// We need to pass in a subkey here...
return new RequiredInputParcel(RequiredInputType.NFC_MOVE_KEY_TO_CARD,
- inputHashes, null, null, mMasterKeyId, buf.getLong());
+ inputData, null, null, mMasterKeyId, buf.getLong());
}
public void addSubkey(long subkeyId) {
@@ -237,6 +261,14 @@ public class RequiredInputParcel implements Parcelable {
mSubkeysToExport.add(subKeyId);
}
+ public void setPin(Passphrase pin) {
+ mPin = pin.toStringUnsafe().getBytes();
+ }
+
+ public void setAdminPin(Passphrase adminPin) {
+ mAdminPin = adminPin.toStringUnsafe().getBytes();
+ }
+
public void addAll(RequiredInputParcel input) {
if (!mMasterKeyId.equals(input.mMasterKeyId)) {
throw new AssertionError("Master keys must match, this is a programming error!");
@@ -245,7 +277,7 @@ public class RequiredInputParcel implements Parcelable {
throw new AssertionError("Operation types must match, this is a programming error!");
}
- Collections.addAll(mSubkeysToExport, input.mInputHashes);
+ Collections.addAll(mSubkeysToExport, input.mInputData);
}
public boolean isEmpty() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java
new file mode 100644
index 000000000..2d9ac6ee3
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2015 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 java.util.ArrayList;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.util.ExportHelper;
+
+public class BackupFragment extends Fragment {
+
+ // This ids for multiple key export.
+ private ArrayList<Long> mIdsForRepeatAskPassphrase;
+ // This index for remembering the number of master key.
+ private int mIndex;
+
+ static final int REQUEST_REPEAT_PASSPHRASE = 1;
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.backup_fragment, container, false);
+
+ View backupAll = view.findViewById(R.id.backup_all);
+ View backupPublicKeys = view.findViewById(R.id.backup_public_keys);
+
+ backupAll.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ exportToFile(true);
+ }
+ });
+
+ backupPublicKeys.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ exportToFile(false);
+ }
+ });
+
+ return view;
+ }
+
+ private void exportToFile(boolean includeSecretKeys) {
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ if (!includeSecretKeys) {
+ ExportHelper exportHelper = new ExportHelper(activity);
+ exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, false);
+ return;
+ }
+
+ new AsyncTask<ContentResolver,Void,ArrayList<Long>>() {
+ @Override
+ protected ArrayList<Long> doInBackground(ContentResolver... resolver) {
+ ArrayList<Long> askPassphraseIds = new ArrayList<>();
+ Cursor cursor = resolver[0].query(
+ KeyRings.buildUnifiedKeyRingsUri(), new String[] {
+ KeyRings.MASTER_KEY_ID,
+ KeyRings.HAS_SECRET,
+ }, KeyRings.HAS_SECRET + " != 0", null, null);
+ try {
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ SecretKeyType secretKeyType = SecretKeyType.fromNum(cursor.getInt(1));
+ switch (secretKeyType) {
+ // all of these make no sense to ask
+ case PASSPHRASE_EMPTY:
+ case GNU_DUMMY:
+ case DIVERT_TO_CARD:
+ case UNAVAILABLE:
+ continue;
+ default: {
+ long keyId = cursor.getLong(0);
+ askPassphraseIds.add(keyId);
+ }
+ }
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return askPassphraseIds;
+ }
+
+ @Override
+ protected void onPostExecute(ArrayList<Long> askPassphraseIds) {
+ super.onPostExecute(askPassphraseIds);
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ mIdsForRepeatAskPassphrase = askPassphraseIds;
+ mIndex = 0;
+
+ if (mIdsForRepeatAskPassphrase.size() != 0) {
+ startPassphraseActivity();
+ return;
+ }
+
+ ExportHelper exportHelper = new ExportHelper(activity);
+ exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
+ }
+
+ }.execute(activity.getContentResolver());
+
+ }
+
+ private void startPassphraseActivity() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ Intent intent = new Intent(activity, PassphraseDialogActivity.class);
+ long masterKeyId = mIdsForRepeatAskPassphrase.get(mIndex++);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, masterKeyId);
+ startActivityForResult(intent, REQUEST_REPEAT_PASSPHRASE);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_REPEAT_PASSPHRASE) {
+ if (resultCode != Activity.RESULT_OK) {
+ return;
+ }
+ if (mIndex < mIdsForRepeatAskPassphrase.size()) {
+ startPassphraseActivity();
+ return;
+ }
+
+ ExportHelper exportHelper = new ExportHelper(getActivity());
+ exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java
index 891c2268c..5ca7c6bc7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java
@@ -49,13 +49,17 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter;
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Preferences;
import java.util.ArrayList;
+import java.util.Date;
public class CertifyKeyFragment
extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult>
@@ -151,7 +155,7 @@ public class CertifyKeyFragment
// make certify image gray, like action icons
ImageView vActionCertifyImage =
(ImageView) view.findViewById(R.id.certify_key_action_certify_image);
- vActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
+ vActionCertifyImage.setColorFilter(FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorTertiaryText),
PorterDuff.Mode.SRC_IN);
View vCertifyButton = view.findViewById(R.id.certify_key_certify_button);
@@ -164,7 +168,7 @@ public class CertifyKeyFragment
Notify.create(getActivity(), getString(R.string.select_key_to_certify),
Notify.Style.ERROR).show();
} else {
- cryptoOperation();
+ cryptoOperation(new CryptoInputParcel(new Date()));
}
}
});
@@ -311,6 +315,11 @@ public class CertifyKeyFragment
CertifyActionsParcel actionsParcel = new CertifyActionsParcel(selectedKeyId);
actionsParcel.mCertifyActions.addAll(certifyActions);
+ if (mUploadKeyCheckbox.isChecked()) {
+ actionsParcel.keyServerUri = Preferences.getPreferences(getActivity())
+ .getPreferredKeyserver();
+ }
+
// cached for next cryptoOperation loop
cacheActionsParcel(actionsParcel);
@@ -318,16 +327,15 @@ public class CertifyKeyFragment
}
@Override
- public void onCryptoOperationSuccess(CertifyResult result) {
+ public void onQueuedOperationSuccess(CertifyResult result) {
+ // protected by Queueing*Fragment
+ Activity activity = getActivity();
+
Intent intent = new Intent();
intent.putExtra(CertifyResult.EXTRA_RESULT, result);
- getActivity().setResult(Activity.RESULT_OK, intent);
- getActivity().finish();
- }
+ activity.setResult(Activity.RESULT_OK, intent);
+ activity.finish();
- @Override
- public void onCryptoOperationCancelled() {
- super.onCryptoOperationCancelled();
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java
index 7a473e49f..ff5fb7cca 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java
@@ -53,7 +53,7 @@ public class ConsolidateDialogActivity extends FragmentActivity
mRecovery = recovery;
- mConsolidateOpHelper = new CryptoOperationHelper<>(this, this, R.string.progress_importing);
+ mConsolidateOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_importing);
mConsolidateOpHelper.cryptoOperation();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
index 505638c7c..83b176680 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
@@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.io.IOException;
@@ -42,7 +43,9 @@ public class CreateKeyActivity extends BaseNfcActivity {
public static final String EXTRA_FIRST_TIME = "first_time";
public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails";
public static final String EXTRA_PASSPHRASE = "passphrase";
- public static final String EXTRA_USE_SMART_CARD_SETTINGS = "use_smart_card_settings";
+ public static final String EXTRA_CREATE_YUBI_KEY = "create_yubi_key";
+ public static final String EXTRA_YUBI_KEY_PIN = "yubi_key_pin";
+ public static final String EXTRA_YUBI_KEY_ADMIN_PIN = "yubi_key_admin_pin";
public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
public static final String EXTRA_NFC_AID = "nfc_aid";
@@ -55,10 +58,17 @@ public class CreateKeyActivity extends BaseNfcActivity {
ArrayList<String> mAdditionalEmails;
Passphrase mPassphrase;
boolean mFirstTime;
- boolean mUseSmartCardSettings;
+ boolean mCreateYubiKey;
+ Passphrase mYubiKeyPin;
+ Passphrase mYubiKeyAdminPin;
Fragment mCurrentFragment;
+
+ byte[] mScannedFingerprints;
+ byte[] mNfcAid;
+ String mNfcUserId;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -66,13 +76,8 @@ public class CreateKeyActivity extends BaseNfcActivity {
// React on NDEF_DISCOVERED from Manifest
// NOTE: ACTION_NDEF_DISCOVERED and not ACTION_TAG_DISCOVERED like in BaseNfcActivity
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
- try {
- handleTagDiscoveredIntent(getIntent());
- } catch (CardException e) {
- handleNfcError(e);
- } catch (IOException e) {
- handleNfcError(e);
- }
+
+ handleIntentInBackground(getIntent());
setTitle(R.string.title_manage_my_keys);
@@ -88,7 +93,9 @@ public class CreateKeyActivity extends BaseNfcActivity {
mAdditionalEmails = savedInstanceState.getStringArrayList(EXTRA_ADDITIONAL_EMAILS);
mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE);
mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME);
- mUseSmartCardSettings = savedInstanceState.getBoolean(EXTRA_USE_SMART_CARD_SETTINGS);
+ mCreateYubiKey = savedInstanceState.getBoolean(EXTRA_CREATE_YUBI_KEY);
+ mYubiKeyPin = savedInstanceState.getParcelable(EXTRA_YUBI_KEY_PIN);
+ mYubiKeyAdminPin = savedInstanceState.getParcelable(EXTRA_YUBI_KEY_ADMIN_PIN);
mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
} else {
@@ -98,7 +105,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
mName = intent.getStringExtra(EXTRA_NAME);
mEmail = intent.getStringExtra(EXTRA_EMAIL);
mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
- mUseSmartCardSettings = intent.getBooleanExtra(EXTRA_USE_SMART_CARD_SETTINGS, false);
+ mCreateYubiKey = intent.getBooleanExtra(EXTRA_CREATE_YUBI_KEY, false);
if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) {
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
@@ -106,15 +113,19 @@ public class CreateKeyActivity extends BaseNfcActivity {
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
if (containsKeys(nfcFingerprints)) {
- Fragment frag = CreateKeyYubiKeyImportFragment.newInstance(
+ Fragment frag = CreateYubiKeyImportFragment.newInstance(
nfcFingerprints, nfcAid, nfcUserId);
loadFragment(frag, FragAction.START);
setTitle(R.string.title_import_keys);
} else {
- Fragment frag = CreateKeyYubiKeyBlankFragment.newInstance();
- loadFragment(frag, FragAction.START);
- setTitle(R.string.title_manage_my_keys);
+// Fragment frag = CreateYubiKeyBlankFragment.newInstance();
+// loadFragment(frag, FragAction.START);
+// setTitle(R.string.title_manage_my_keys);
+ Notify.create(this,
+ "YubiKey key creation is currently not supported. Please follow our FAQ.",
+ Notify.Style.ERROR
+ ).show();
}
// done
@@ -136,40 +147,51 @@ public class CreateKeyActivity extends BaseNfcActivity {
}
@Override
- protected void onNfcPerform() throws IOException {
+ protected void doNfcInBackground() throws IOException {
if (mCurrentFragment instanceof NfcListenerFragment) {
- ((NfcListenerFragment) mCurrentFragment).onNfcPerform();
+ ((NfcListenerFragment) mCurrentFragment).doNfcInBackground();
return;
}
- byte[] scannedFingerprints = nfcGetFingerprints();
- byte[] nfcAid = nfcGetAid();
- String userId = nfcGetUserId();
+ mScannedFingerprints = nfcGetFingerprints();
+ mNfcAid = nfcGetAid();
+ mNfcUserId = nfcGetUserId();
+ }
- if (containsKeys(scannedFingerprints)) {
+ @Override
+ protected void onNfcPostExecute() throws IOException {
+ if (mCurrentFragment instanceof NfcListenerFragment) {
+ ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute();
+ return;
+ }
+
+ if (containsKeys(mScannedFingerprints)) {
try {
- long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints);
+ long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mScannedFingerprints);
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
ring.getMasterKeyId();
Intent intent = new Intent(this, ViewKeyActivity.class);
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mScannedFingerprints);
startActivity(intent);
finish();
} catch (PgpKeyNotFoundException e) {
- Fragment frag = CreateKeyYubiKeyImportFragment.newInstance(
- scannedFingerprints, nfcAid, userId);
+ Fragment frag = CreateYubiKeyImportFragment.newInstance(
+ mScannedFingerprints, mNfcAid, mNfcUserId);
loadFragment(frag, FragAction.TO_RIGHT);
}
} else {
- Fragment frag = CreateKeyYubiKeyBlankFragment.newInstance();
- loadFragment(frag, FragAction.TO_RIGHT);
+// Fragment frag = CreateYubiKeyBlankFragment.newInstance();
+// loadFragment(frag, FragAction.TO_RIGHT);
+ Notify.create(this,
+ "YubiKey key creation is currently not supported. Please follow our FAQ.",
+ Notify.Style.ERROR
+ ).show();
}
-
}
private boolean containsKeys(byte[] scannedFingerprints) {
@@ -193,7 +215,9 @@ public class CreateKeyActivity extends BaseNfcActivity {
outState.putStringArrayList(EXTRA_ADDITIONAL_EMAILS, mAdditionalEmails);
outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase);
outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime);
- outState.putBoolean(EXTRA_USE_SMART_CARD_SETTINGS, mUseSmartCardSettings);
+ outState.putBoolean(EXTRA_CREATE_YUBI_KEY, mCreateYubiKey);
+ outState.putParcelable(EXTRA_YUBI_KEY_PIN, mYubiKeyPin);
+ outState.putParcelable(EXTRA_YUBI_KEY_ADMIN_PIN, mYubiKeyAdminPin);
}
@Override
@@ -238,7 +262,8 @@ public class CreateKeyActivity extends BaseNfcActivity {
}
interface NfcListenerFragment {
- public void onNfcPerform() throws IOException;
+ public void doNfcInBackground() throws IOException;
+ public void onNfcPostExecute() throws IOException;
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java
index 597f04d6b..acb768f55 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
+import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -29,6 +30,7 @@ import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
@@ -37,7 +39,6 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.dialog.AddEmailDialogFragment;
-import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.widget.EmailEditText;
@@ -201,7 +202,7 @@ public class CreateKeyEmailFragment extends Fragment {
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
- if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) {
+ if (message.what == AddEmailDialogFragment.MESSAGE_OKAY) {
Bundle data = message.getData();
String email = data.getString(AddEmailDialogFragment.MESSAGE_DATA_EMAIL);
@@ -232,11 +233,35 @@ public class CreateKeyEmailFragment extends Fragment {
mCreateKeyActivity.mEmail = mEmailEdit.getText().toString();
mCreateKeyActivity.mAdditionalEmails = getAdditionalEmails();
- CreateKeyPassphraseFragment frag = CreateKeyPassphraseFragment.newInstance();
- mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
+ CreateKeyActivity createKeyActivity = ((CreateKeyActivity) getActivity());
+
+ if (createKeyActivity.mCreateYubiKey) {
+ hideKeyboard();
+
+ CreateYubiKeyPinFragment frag = CreateYubiKeyPinFragment.newInstance();
+ mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
+ } else {
+ CreateKeyPassphraseFragment frag = CreateKeyPassphraseFragment.newInstance();
+ mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
+ }
}
}
+ private void hideKeyboard() {
+ if (getActivity() == null) {
+ return;
+ }
+ InputMethodManager inputManager = (InputMethodManager) getActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ // check if no view has focus
+ View v = getActivity().getCurrentFocus();
+ if (v == null)
+ return;
+
+ inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+
private ArrayList<String> getAdditionalEmails() {
ArrayList<String> emails = new ArrayList<>();
for (EmailAdapter.ViewModel holder : mAdditionalEmailModels) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
index e67715988..739eb3e35 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
@@ -17,6 +17,10 @@
package org.sufficientlysecure.keychain.ui;
+
+import java.util.Date;
+import java.util.Iterator;
+
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
@@ -44,13 +48,13 @@ import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
-import java.util.Iterator;
-
public class CreateKeyFinalFragment extends Fragment {
public static final int REQUEST_EDIT_KEY = 0x00008007;
@@ -69,8 +73,14 @@ public class CreateKeyFinalFragment extends Fragment {
private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mCreateOpHelper;
private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mMoveToCardOpHelper;
+ // queued results which may trigger delayed actions
+ private EditKeyResult mQueuedSaveKeyResult;
+ private OperationResult mQueuedFinishResult;
+ private EditKeyResult mQueuedDisplayResult;
+
public static CreateKeyFinalFragment newInstance() {
CreateKeyFinalFragment frag = new CreateKeyFinalFragment();
+ frag.setRetainInstance(true);
Bundle args = new Bundle();
frag.setArguments(args);
@@ -178,7 +188,7 @@ public class CreateKeyFinalFragment extends Fragment {
if (mSaveKeyringParcel == null) {
mSaveKeyringParcel = new SaveKeyringParcel();
- if (createKeyActivity.mUseSmartCardSettings) {
+ if (createKeyActivity.mCreateYubiKey) {
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
2048, null, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER, 0L));
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
@@ -187,6 +197,9 @@ public class CreateKeyFinalFragment extends Fragment {
2048, null, KeyFlags.AUTHENTICATION, 0L));
mEditText.setText(R.string.create_key_custom);
mEditButton.setEnabled(false);
+
+ // use empty passphrase
+ mSaveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase(), null);
} else {
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
4096, null, KeyFlags.CERTIFY_OTHER, 0L));
@@ -194,6 +207,10 @@ public class CreateKeyFinalFragment extends Fragment {
4096, null, KeyFlags.SIGN_DATA, 0L));
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
+
+ mSaveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null
+ ? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null)
+ : null;
}
String userId = KeyRing.createUserId(
new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null)
@@ -209,14 +226,44 @@ public class CreateKeyFinalFragment extends Fragment {
mSaveKeyringParcel.mAddUserIds.add(thisUserId);
}
}
- mSaveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null
- ? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null)
- : null;
}
+
+ // handle queued actions
+
+ if (mQueuedFinishResult != null) {
+ finishWithResult(mQueuedFinishResult);
+ return;
+ }
+
+ if (mQueuedDisplayResult != null) {
+ try {
+ displayResult(mQueuedDisplayResult);
+ } finally {
+ // clear after operation, note that this may drop the operation if it didn't
+ // work when called from here!
+ mQueuedDisplayResult = null;
+ }
+ }
+
+ if (mQueuedSaveKeyResult != null) {
+ try {
+ uploadKey(mQueuedSaveKeyResult);
+ } finally {
+ // see above
+ mQueuedSaveKeyResult = null;
+ }
+ }
+
}
private void createKey() {
- final CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity();
+ CreateKeyActivity activity = (CreateKeyActivity) getActivity();
+ if (activity == null) {
+ // this is a ui-triggered action, nvm if it fails while detached!
+ return;
+ }
+
+ final boolean createYubiKey = activity.mCreateYubiKey;
CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> createKeyCallback
= new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() {
@@ -228,7 +275,7 @@ public class CreateKeyFinalFragment extends Fragment {
@Override
public void onCryptoOperationSuccess(EditKeyResult result) {
- if (createKeyActivity.mUseSmartCardSettings) {
+ if (createYubiKey) {
moveToCard(result);
return;
}
@@ -239,10 +286,7 @@ public class CreateKeyFinalFragment extends Fragment {
return;
}
- Intent data = new Intent();
- data.putExtra(OperationResult.EXTRA_RESULT, result);
- getActivity().setResult(Activity.RESULT_OK, data);
- getActivity().finish();
+ finishWithResult(result);
}
@Override
@@ -252,7 +296,7 @@ public class CreateKeyFinalFragment extends Fragment {
@Override
public void onCryptoOperationError(EditKeyResult result) {
- result.createNotify(getActivity()).show();
+ displayResult(result);
}
@Override
@@ -261,16 +305,25 @@ public class CreateKeyFinalFragment extends Fragment {
}
};
- mCreateOpHelper = new CryptoOperationHelper<>(this, createKeyCallback,
- R.string.progress_building_key);
+ mCreateOpHelper = new CryptoOperationHelper<>(1, this, createKeyCallback, R.string.progress_building_key);
mCreateOpHelper.cryptoOperation();
}
+ private void displayResult(EditKeyResult result) {
+ Activity activity = getActivity();
+ if (activity == null) {
+ mQueuedDisplayResult = result;
+ return;
+ }
+ result.createNotify(activity).show();
+ }
+
private void moveToCard(final EditKeyResult saveKeyResult) {
- CachedPublicKeyRing key = (new ProviderHelper(getActivity()))
- .getCachedPublicKeyRing(saveKeyResult.mMasterKeyId);
+ CreateKeyActivity activity = (CreateKeyActivity) getActivity();
final SaveKeyringParcel changeKeyringParcel;
+ CachedPublicKeyRing key = (new ProviderHelper(activity))
+ .getCachedPublicKeyRing(saveKeyResult.mMasterKeyId);
try {
changeKeyringParcel = new SaveKeyringParcel(key.getMasterKeyId(), key.getFingerprint());
} catch (PgpKeyNotFoundException e) {
@@ -278,9 +331,10 @@ public class CreateKeyFinalFragment extends Fragment {
return;
}
- Cursor cursor = getActivity().getContentResolver().query(
+ // define subkeys that should be moved to the card
+ Cursor cursor = activity.getContentResolver().query(
KeychainContract.Keys.buildKeysUri(changeKeyringParcel.mMasterKeyId),
- new String[]{KeychainContract.Keys.KEY_ID,}, null, null, null
+ new String[] { KeychainContract.Keys.KEY_ID, }, null, null, null
);
try {
while (cursor != null && cursor.moveToNext()) {
@@ -293,6 +347,10 @@ public class CreateKeyFinalFragment extends Fragment {
}
}
+ // define new PIN and Admin PIN for the card
+ changeKeyringParcel.mCardPin = activity.mYubiKeyPin;
+ changeKeyringParcel.mCardAdminPin = activity.mYubiKeyAdminPin;
+
CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> callback
= new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() {
@@ -326,10 +384,7 @@ public class CreateKeyFinalFragment extends Fragment {
return;
}
- Intent data = new Intent();
- data.putExtra(OperationResult.EXTRA_RESULT, saveKeyResult);
- getActivity().setResult(Activity.RESULT_OK, data);
- getActivity().finish();
+ finishWithResult(saveKeyResult);
}
@Override
@@ -339,16 +394,22 @@ public class CreateKeyFinalFragment extends Fragment {
};
- mMoveToCardOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_modify);
- mMoveToCardOpHelper.cryptoOperation();
+ mMoveToCardOpHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_modify);
+ mMoveToCardOpHelper.cryptoOperation(new CryptoInputParcel(new Date()));
}
private void uploadKey(final EditKeyResult saveKeyResult) {
+ Activity activity = getActivity();
+ // if the activity is gone at this point, there is nothing we can do!
+ if (activity == null) {
+ mQueuedSaveKeyResult = saveKeyResult;
+ return;
+ }
+
// set data uri as path to keyring
- final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(
- saveKeyResult.mMasterKeyId);
+ final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(saveKeyResult.mMasterKeyId);
// upload to favorite keyserver
- final String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
+ final String keyserver = Preferences.getPreferences(activity).getPreferredKeyserver();
CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> callback
= new CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult>() {
@@ -374,14 +435,8 @@ public class CreateKeyFinalFragment extends Fragment {
}
public void handleResult(ExportResult result) {
- // TODO: ExportOperation UPLOAD_KEYSERVER needs logs!
- // TODO: then merge logs here!
- //saveKeyResult.getLog().add(result, 0);
-
- Intent data = new Intent();
- data.putExtra(OperationResult.EXTRA_RESULT, saveKeyResult);
- getActivity().setResult(Activity.RESULT_OK, data);
- getActivity().finish();
+ saveKeyResult.getLog().add(result, 0);
+ finishWithResult(saveKeyResult);
}
@Override
@@ -390,9 +445,21 @@ public class CreateKeyFinalFragment extends Fragment {
}
};
-
- mUploadOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_uploading);
+ mUploadOpHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_uploading);
mUploadOpHelper.cryptoOperation();
}
+ public void finishWithResult(OperationResult result) {
+ Activity activity = getActivity();
+ if (activity == null) {
+ mQueuedFinishResult = result;
+ return;
+ }
+
+ Intent data = new Intent();
+ data.putExtra(OperationResult.EXTRA_RESULT, result);
+ activity.setResult(Activity.RESULT_OK, data);
+ activity.finish();
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java
index 3379e0a6d..d858fd6ec 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java
@@ -21,6 +21,8 @@ import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
+import android.text.Editable;
+import android.text.TextWatcher;
import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
import android.view.LayoutInflater;
@@ -79,19 +81,10 @@ public class CreateKeyPassphraseFragment extends Fragment {
return output;
}
- private static boolean areEditTextsEqual(Context context, EditText editText1, EditText editText2) {
+ private static boolean areEditTextsEqual(EditText editText1, EditText editText2) {
Passphrase p1 = new Passphrase(editText1);
Passphrase p2 = new Passphrase(editText2);
- boolean output = (p1.equals(p2));
-
- if (!output) {
- editText2.setError(context.getString(R.string.create_key_passphrases_not_equal));
- editText2.requestFocus();
- } else {
- editText2.setError(null);
- }
-
- return output;
+ return (p1.equals(p2));
}
@Override
@@ -107,8 +100,8 @@ public class CreateKeyPassphraseFragment extends Fragment {
// initial values
// TODO: using String here is unsafe...
if (mCreateKeyActivity.mPassphrase != null) {
- mPassphraseEdit.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray()));
- mPassphraseEditAgain.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray()));
+ mPassphraseEdit.setText(mCreateKeyActivity.mPassphrase.toStringUnsafe());
+ mPassphraseEditAgain.setText(mCreateKeyActivity.mPassphrase.toStringUnsafe());
}
mPassphraseEdit.requestFocus();
@@ -137,6 +130,35 @@ public class CreateKeyPassphraseFragment extends Fragment {
}
});
+ TextWatcher textWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ if (!isEditTextNotEmpty(getActivity(), mPassphraseEdit)) {
+ mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ return;
+ }
+
+ if (areEditTextsEqual(mPassphraseEdit, mPassphraseEditAgain)) {
+ mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_stat_retyped_ok, 0);
+ } else {
+ mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_stat_retyped_bad, 0);
+ }
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+
+ }
+ };
+
+ mPassphraseEdit.addTextChangedListener(textWatcher);
+ mPassphraseEditAgain.addTextChangedListener(textWatcher);
return view;
}
@@ -153,9 +175,15 @@ public class CreateKeyPassphraseFragment extends Fragment {
}
private void nextClicked() {
- if (isEditTextNotEmpty(getActivity(), mPassphraseEdit)
- && areEditTextsEqual(getActivity(), mPassphraseEdit, mPassphraseEditAgain)) {
+ if (isEditTextNotEmpty(getActivity(), mPassphraseEdit)) {
+
+ if (!areEditTextsEqual(mPassphraseEdit, mPassphraseEditAgain)) {
+ mPassphraseEditAgain.setError(getActivity().getApplicationContext().getString(R.string.create_key_passphrases_not_equal));
+ mPassphraseEditAgain.requestFocus();
+ return;
+ }
+ mPassphraseEditAgain.setError(null);
// save state
mCreateKeyActivity.mPassphrase = new Passphrase(mPassphraseEdit);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java
index 1a844e6e4..cd97ef108 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java
@@ -81,7 +81,7 @@ public class CreateKeyStartFragment extends Fragment {
mYubiKey.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- CreateKeyYubiKeyWaitFragment frag = new CreateKeyYubiKeyWaitFragment();
+ CreateYubiKeyWaitFragment frag = new CreateYubiKeyWaitFragment();
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
}
});
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java
index 6df340636..5b13dc88e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyBlankFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java
@@ -27,7 +27,7 @@ import android.view.ViewGroup;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
-public class CreateKeyYubiKeyBlankFragment extends Fragment {
+public class CreateYubiKeyBlankFragment extends Fragment {
CreateKeyActivity mCreateKeyActivity;
View mBackButton;
@@ -36,8 +36,8 @@ public class CreateKeyYubiKeyBlankFragment extends Fragment {
/**
* Creates new instance of this fragment
*/
- public static CreateKeyYubiKeyBlankFragment newInstance() {
- CreateKeyYubiKeyBlankFragment frag = new CreateKeyYubiKeyBlankFragment();
+ public static CreateYubiKeyBlankFragment newInstance() {
+ CreateYubiKeyBlankFragment frag = new CreateYubiKeyBlankFragment();
Bundle args = new Bundle();
@@ -81,7 +81,7 @@ public class CreateKeyYubiKeyBlankFragment extends Fragment {
}
private void nextClicked() {
- mCreateKeyActivity.mUseSmartCardSettings = true;
+ mCreateKeyActivity.mCreateYubiKey = true;
CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance();
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java
index 8de874ec5..d88e6b9f9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java
@@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -39,13 +40,13 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment;
-import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
+import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Preferences;
-public class CreateKeyYubiKeyImportFragment
- extends CryptoOperationFragment<ImportKeyringParcel, ImportKeyResult>
+public class CreateYubiKeyImportFragment
+ extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult>
implements NfcListenerFragment {
private static final String ARG_FINGERPRINT = "fingerprint";
@@ -68,7 +69,7 @@ public class CreateKeyYubiKeyImportFragment
public static Fragment newInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) {
- CreateKeyYubiKeyImportFragment frag = new CreateKeyYubiKeyImportFragment();
+ CreateYubiKeyImportFragment frag = new CreateYubiKeyImportFragment();
Bundle args = new Bundle();
args.putByteArray(ARG_FINGERPRINT, scannedFingerprints);
@@ -195,7 +196,7 @@ public class CreateKeyYubiKeyImportFragment
}
@Override
- public void onNfcPerform() throws IOException {
+ public void doNfcInBackground() throws IOException {
mNfcFingerprints = mCreateKeyActivity.nfcGetFingerprints();
mNfcAid = mCreateKeyActivity.nfcGetAid();
@@ -204,10 +205,14 @@ public class CreateKeyYubiKeyImportFragment
byte[] fp = new byte[20];
ByteBuffer.wrap(fp).put(mNfcFingerprints, 0, 20);
mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(fp);
+ }
+
+ @Override
+ public void onNfcPostExecute() throws IOException {
setData();
- refreshSearch();
+ refreshSearch();
}
@Override
@@ -216,14 +221,17 @@ public class CreateKeyYubiKeyImportFragment
}
@Override
- public void onCryptoOperationSuccess(ImportKeyResult result) {
+ public void onQueuedOperationSuccess(ImportKeyResult result) {
long[] masterKeyIds = result.getImportedMasterKeyIds();
if (masterKeyIds.length == 0) {
super.onCryptoOperationError(result);
return;
}
- Intent intent = new Intent(getActivity(), ViewKeyActivity.class);
+ // null-protected from Queueing*Fragment
+ Activity activity = getActivity();
+
+ Intent intent = new Intent(activity, ViewKeyActivity.class);
// use the imported masterKeyId, not the one from the yubikey, because
// that one might* just have been a subkey of the imported key
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0]));
@@ -232,6 +240,6 @@ public class CreateKeyYubiKeyImportFragment
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints);
startActivity(intent);
- getActivity().finish();
+ activity.finish();
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java
new file mode 100644
index 000000000..a793b31f2
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 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.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
+import org.sufficientlysecure.keychain.util.Passphrase;
+
+import java.security.SecureRandom;
+
+public class CreateYubiKeyPinFragment extends Fragment {
+
+ // view
+ CreateKeyActivity mCreateKeyActivity;
+ TextView mPin;
+ TextView mAdminPin;
+ View mBackButton;
+ View mNextButton;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static CreateYubiKeyPinFragment newInstance() {
+ CreateYubiKeyPinFragment frag = new CreateYubiKeyPinFragment();
+
+ Bundle args = new Bundle();
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.create_yubi_key_pin_fragment, container, false);
+
+ mPin = (TextView) view.findViewById(R.id.create_yubi_key_pin);
+ mAdminPin = (TextView) view.findViewById(R.id.create_yubi_key_admin_pin);
+ mBackButton = view.findViewById(R.id.create_key_back_button);
+ mNextButton = view.findViewById(R.id.create_key_next_button);
+
+ if (mCreateKeyActivity.mYubiKeyPin == null) {
+ new AsyncTask<Void, Void, Pair<Passphrase, Passphrase>>() {
+ @Override
+ protected Pair<Passphrase, Passphrase> doInBackground(Void... unused) {
+ SecureRandom secureRandom = new SecureRandom();
+ // min = 6, we choose 6
+ String pin = "" + secureRandom.nextInt(9)
+ + secureRandom.nextInt(9)
+ + secureRandom.nextInt(9)
+ + secureRandom.nextInt(9)
+ + secureRandom.nextInt(9)
+ + secureRandom.nextInt(9);
+ // min = 8, we choose 10, but 6 are equals the PIN
+ String adminPin = pin + secureRandom.nextInt(9)
+ + secureRandom.nextInt(9)
+ + secureRandom.nextInt(9)
+ + secureRandom.nextInt(9);
+
+ return new Pair<>(new Passphrase(pin), new Passphrase(adminPin));
+ }
+
+ @Override
+ protected void onPostExecute(Pair<Passphrase, Passphrase> pair) {
+ mCreateKeyActivity.mYubiKeyPin = pair.first;
+ mCreateKeyActivity.mYubiKeyAdminPin = pair.second;
+
+ mPin.setText(mCreateKeyActivity.mYubiKeyPin.toStringUnsafe());
+ mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe());
+ }
+ }.execute();
+ } else {
+ mPin.setText(mCreateKeyActivity.mYubiKeyPin.toStringUnsafe());
+ mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe());
+ }
+
+ mBackButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ back();
+ }
+ });
+ mNextButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ nextClicked();
+ }
+ });
+
+
+ return view;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mCreateKeyActivity = (CreateKeyActivity) getActivity();
+ }
+
+
+ private void nextClicked() {
+ CreateYubiKeyPinRepeatFragment frag = CreateYubiKeyPinRepeatFragment.newInstance();
+ mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
+ }
+
+ private void back() {
+ mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java
new file mode 100644
index 000000000..2e752e609
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015 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.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
+
+public class CreateYubiKeyPinRepeatFragment extends Fragment {
+
+ // view
+ CreateKeyActivity mCreateKeyActivity;
+ EditText mPin;
+ EditText mAdminPin;
+ View mBackButton;
+ View mNextButton;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static CreateYubiKeyPinRepeatFragment newInstance() {
+ CreateYubiKeyPinRepeatFragment frag = new CreateYubiKeyPinRepeatFragment();
+
+ Bundle args = new Bundle();
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Checks if text of given EditText is not empty. If it is empty an error is
+ * set and the EditText gets the focus.
+ *
+ * @param context
+ * @param editText
+ * @return true if EditText is not empty
+ */
+ private static boolean isEditTextNotEmpty(Context context, EditText editText) {
+ boolean output = true;
+ if (editText.getText().length() == 0) {
+ editText.setError(context.getString(R.string.create_key_empty));
+ editText.requestFocus();
+ output = false;
+ } else {
+ editText.setError(null);
+ }
+
+ return output;
+ }
+
+ private static boolean checkPin(Context context, EditText editText1, String pin) {
+ boolean output = editText1.getText().toString().equals(pin);
+
+ if (!output) {
+ editText1.setError(context.getString(R.string.create_key_yubi_key_pin_not_correct));
+ editText1.requestFocus();
+ } else {
+ editText1.setError(null);
+ }
+
+ return output;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.create_yubi_key_pin_repeat_fragment, container, false);
+
+ mPin = (EditText) view.findViewById(R.id.create_yubi_key_pin_repeat);
+ mAdminPin = (EditText) view.findViewById(R.id.create_yubi_key_admin_pin_repeat);
+ mBackButton = view.findViewById(R.id.create_key_back_button);
+ mNextButton = view.findViewById(R.id.create_key_next_button);
+
+ mPin.requestFocus();
+ mBackButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ back();
+ }
+ });
+ mNextButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ nextClicked();
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mCreateKeyActivity = (CreateKeyActivity) getActivity();
+ }
+
+ private void back() {
+ hideKeyboard();
+ mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
+ }
+
+ private void nextClicked() {
+ if (isEditTextNotEmpty(getActivity(), mPin)
+ && checkPin(getActivity(), mPin, mCreateKeyActivity.mYubiKeyPin.toStringUnsafe())
+ && isEditTextNotEmpty(getActivity(), mAdminPin)
+ && checkPin(getActivity(), mAdminPin, mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe())) {
+
+ CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance();
+ hideKeyboard();
+ mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
+ }
+ }
+
+ private void hideKeyboard() {
+ if (getActivity() == null) {
+ return;
+ }
+ InputMethodManager inputManager = (InputMethodManager) getActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ // check if no view has focus
+ View v = getActivity().getCurrentFocus();
+ if (v == null)
+ return;
+
+ inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java
index 0e2dd2531..d45195512 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java
@@ -28,7 +28,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
-public class CreateKeyYubiKeyWaitFragment extends Fragment {
+public class CreateYubiKeyWaitFragment extends Fragment {
CreateKeyActivity mCreateKeyActivity;
View mBackButton;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
index 04f54f151..e581e069b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
@@ -49,13 +49,7 @@ public class DecryptActivity extends BaseActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setFullScreenDialogClose(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- setResult(Activity.RESULT_CANCELED);
- finish();
- }
- }, false);
+ setFullScreenDialogClose(Activity.RESULT_CANCELED, false);
// Handle intent actions
handleActions(savedInstanceState, getIntent());
@@ -158,7 +152,6 @@ public class DecryptActivity extends BaseActivity {
}
-
} catch (IOException e) {
Toast.makeText(this, R.string.error_reading_text, Toast.LENGTH_LONG).show();
finish();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
index 4eb8cd5e8..aaf337f42 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
@@ -19,15 +19,11 @@ package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList;
-import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
import android.support.v4.app.Fragment;
-import android.os.Parcelable;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
@@ -52,7 +48,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
-import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
@@ -189,7 +184,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager.
}
};
- mImportOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_importing);
+ mImportOpHelper = new CryptoOperationHelper<>(1, this, callback, R.string.progress_importing);
mImportOpHelper.cryptoOperation();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
index 96767463e..567589821 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
@@ -65,12 +65,11 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
-import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
// this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15)
+import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder;
import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel;
import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
-import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
@@ -81,18 +80,21 @@ import org.sufficientlysecure.keychain.util.ParcelableHashMap;
public class DecryptListFragment
- extends CryptoOperationFragment<PgpDecryptVerifyInputParcel,DecryptVerifyResult>
+ extends QueueingCryptoOperationFragment<PgpDecryptVerifyInputParcel,DecryptVerifyResult>
implements OnMenuItemClickListener {
public static final String ARG_INPUT_URIS = "input_uris";
public static final String ARG_OUTPUT_URIS = "output_uris";
+ public static final String ARG_CANCELLED_URIS = "cancelled_uris";
public static final String ARG_RESULTS = "results";
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
+ public static final String ARG_CURRENT_URI = "current_uri";
private ArrayList<Uri> mInputUris;
private HashMap<Uri, Uri> mOutputUris;
private ArrayList<Uri> mPendingInputUris;
+ private ArrayList<Uri> mCancelledInputUris;
private Uri mCurrentInputUri;
@@ -111,6 +113,10 @@ public class DecryptListFragment
return frag;
}
+ public DecryptListFragment() {
+ super(null);
+ }
+
/**
* Inflate the layout for this fragment
*/
@@ -152,6 +158,8 @@ public class DecryptListFragment
outState.putParcelable(ARG_RESULTS, new ParcelableHashMap<>(results));
outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mOutputUris));
+ outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris);
+ outState.putParcelable(ARG_CURRENT_URI, mCurrentInputUri);
}
@@ -162,25 +170,45 @@ public class DecryptListFragment
Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS);
+ ArrayList<Uri> cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS);
ParcelableHashMap<Uri,Uri> outputUris = args.getParcelable(ARG_OUTPUT_URIS);
ParcelableHashMap<Uri,DecryptVerifyResult> results = args.getParcelable(ARG_RESULTS);
+ Uri currentInputUri = args.getParcelable(ARG_CURRENT_URI);
- displayInputUris(inputUris,
+ displayInputUris(inputUris, currentInputUri, cancelledUris,
outputUris != null ? outputUris.getMap() : null,
results != null ? results.getMap() : null
);
}
- private void displayInputUris(ArrayList<Uri> inputUris, HashMap<Uri,Uri> outputUris,
+ private void displayInputUris(ArrayList<Uri> inputUris, Uri currentInputUri,
+ ArrayList<Uri> cancelledUris, HashMap<Uri,Uri> outputUris,
HashMap<Uri,DecryptVerifyResult> results) {
mInputUris = inputUris;
+ mCurrentInputUri = currentInputUri;
mOutputUris = outputUris != null ? outputUris : new HashMap<Uri,Uri>(inputUris.size());
+ mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList<Uri>();
mPendingInputUris = new ArrayList<>();
- for (Uri uri : inputUris) {
+ for (final Uri uri : inputUris) {
mAdapter.add(uri);
+
+ if (uri.equals(mCurrentInputUri)) {
+ continue;
+ }
+
+ if (mCancelledInputUris.contains(uri)) {
+ mAdapter.setCancelled(uri, new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ retryUri(uri);
+ }
+ });
+ continue;
+ }
+
if (results != null && results.containsKey(uri)) {
processResult(uri, results.get(uri));
} else {
@@ -189,16 +217,9 @@ public class DecryptListFragment
}
}
- cryptoOperation();
- }
-
- private String removeEncryptedAppend(String name) {
- if (name.endsWith(Constants.FILE_EXTENSION_ASC)
- || name.endsWith(Constants.FILE_EXTENSION_PGP_MAIN)
- || name.endsWith(Constants.FILE_EXTENSION_PGP_ALTERNATE)) {
- return name.substring(0, name.length() - 4);
+ if (mCurrentInputUri == null) {
+ cryptoOperation();
}
- return name;
}
private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) {
@@ -249,17 +270,13 @@ public class DecryptListFragment
}
@Override
- protected void cryptoOperation(CryptoInputParcel cryptoInput) {
- super.cryptoOperation(cryptoInput, false);
- }
-
- @Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
mAdapter.setProgress(mCurrentInputUri, progress, max, msg);
return true;
}
+
@Override
- public void onCryptoOperationError(DecryptVerifyResult result) {
+ public void onQueuedOperationError(DecryptVerifyResult result) {
final Uri uri = mCurrentInputUri;
mCurrentInputUri = null;
@@ -269,7 +286,7 @@ public class DecryptListFragment
}
@Override
- public void onCryptoOperationSuccess(DecryptVerifyResult result) {
+ public void onQueuedOperationSuccess(DecryptVerifyResult result) {
Uri uri = mCurrentInputUri;
mCurrentInputUri = null;
@@ -278,6 +295,25 @@ public class DecryptListFragment
cryptoOperation();
}
+ @Override
+ public void onCryptoOperationCancelled() {
+ super.onCryptoOperationCancelled();
+
+ final Uri uri = mCurrentInputUri;
+ mCurrentInputUri = null;
+
+ mCancelledInputUris.add(uri);
+ mAdapter.setCancelled(uri, new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ retryUri(uri);
+ }
+ });
+
+ cryptoOperation();
+
+ }
+
private void processResult(final Uri uri, final DecryptVerifyResult result) {
new AsyncTask<Void, Void, Drawable>() {
@@ -304,7 +340,7 @@ public class DecryptListFragment
}
final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setType(type);
+ intent.setDataAndType(outputUri, type);
final List<ResolveInfo> matches =
context.getPackageManager().queryIntentActivities(intent, 0);
@@ -361,6 +397,22 @@ public class DecryptListFragment
}
+ public void retryUri(Uri uri) {
+
+ // never interrupt running operations!
+ if (mCurrentInputUri != null) {
+ return;
+ }
+
+ // un-cancel this one
+ mCancelledInputUris.remove(uri);
+ mPendingInputUris.add(uri);
+ mAdapter.setCancelled(uri, null);
+
+ cryptoOperation();
+
+ }
+
public void displayWithViewIntent(final Uri uri) {
Activity activity = getActivity();
if (activity == null || mCurrentInputUri != null) {
@@ -539,12 +591,14 @@ public class DecryptListFragment
int mProgress, mMax;
String mProgressMsg;
+ OnClickListener mCancelled;
ViewModel(Context context, Uri uri) {
mContext = context;
mInputUri = uri;
mProgress = 0;
mMax = 100;
+ mCancelled = null;
}
void addResult(DecryptVerifyResult result) {
@@ -564,6 +618,10 @@ public class DecryptListFragment
return mResult != null;
}
+ void setCancelled(OnClickListener retryListener) {
+ mCancelled = retryListener;
+ }
+
void setProgress(int progress, int max, String msg) {
if (msg != null) {
mProgressMsg = msg;
@@ -621,6 +679,11 @@ public class DecryptListFragment
// - replace the contents of the view with that element
final ViewModel model = mDataset.get(position);
+ if (model.mCancelled != null) {
+ bindItemCancelled(holder, model);
+ return;
+ }
+
if (!model.hasResult()) {
bindItemProgress(holder, model);
return;
@@ -634,6 +697,14 @@ public class DecryptListFragment
}
+ private void bindItemCancelled(ViewHolder holder, ViewModel model) {
+ if (holder.vAnimator.getDisplayedChild() != 3) {
+ holder.vAnimator.setDisplayedChild(3);
+ }
+
+ holder.vCancelledRetry.setOnClickListener(model.mCancelled);
+ }
+
private void bindItemProgress(ViewHolder holder, ViewModel model) {
if (holder.vAnimator.getDisplayedChild() != 0) {
holder.vAnimator.setDisplayedChild(0);
@@ -749,6 +820,13 @@ public class DecryptListFragment
notifyItemChanged(pos);
}
+ public void setCancelled(Uri uri, OnClickListener retryListener) {
+ ViewModel newModel = new ViewModel(mContext, uri);
+ int pos = mDataset.indexOf(newModel);
+ mDataset.get(pos).setCancelled(retryListener);
+ notifyItemChanged(pos);
+ }
+
public void addResult(Uri uri, DecryptVerifyResult result, Drawable icon,
OnClickListener onFileClick, OnClickListener onKeyClick) {
@@ -796,6 +874,8 @@ public class DecryptListFragment
public TextView vErrorMsg;
public ImageView vErrorViewLog;
+ public ImageView vCancelledRetry;
+
public ViewHolder(View itemView) {
super(itemView);
@@ -824,6 +904,8 @@ public class DecryptListFragment
vErrorMsg = (TextView) itemView.findViewById(R.id.result_error_msg);
vErrorViewLog = (ImageView) itemView.findViewById(R.id.result_error_log);
+ vCancelledRetry = (ImageView) itemView.findViewById(R.id.cancel_retry);
+
}
@Override
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java
new file mode 100644
index 000000000..b22053df1
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.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.ui;
+
+import android.app.Activity;
+import android.support.v7.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.DeleteResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.operations.results.RevokeResult;
+import org.sufficientlysecure.keychain.pgp.KeyRing;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.DeleteKeyringParcel;
+import org.sufficientlysecure.keychain.service.RevokeKeyringParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
+import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.Date;
+import java.util.HashMap;
+
+public class DeleteKeyDialogActivity extends FragmentActivity {
+ public static final String EXTRA_DELETE_MASTER_KEY_IDS = "extra_delete_master_key_ids";
+ public static final String EXTRA_HAS_SECRET = "extra_has_secret";
+ public static final String EXTRA_KEYSERVER = "extra_keyserver";
+
+ private CryptoOperationHelper<DeleteKeyringParcel, DeleteResult> mDeleteOpHelper;
+ private CryptoOperationHelper<RevokeKeyringParcel, RevokeResult> mRevokeOpHelper;
+
+ private long[] mMasterKeyIds;
+ private boolean mHasSecret;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mDeleteOpHelper = new CryptoOperationHelper<>(1, DeleteKeyDialogActivity.this,
+ getDeletionCallback(), R.string.progress_deleting);
+
+ mRevokeOpHelper = new CryptoOperationHelper<>(2, this,
+ getRevocationCallback(), R.string.progress_revoking_uploading);
+
+ mMasterKeyIds = getIntent().getLongArrayExtra(EXTRA_DELETE_MASTER_KEY_IDS);
+ mHasSecret = getIntent().getBooleanExtra(EXTRA_HAS_SECRET, false);
+
+ if (mMasterKeyIds.length > 1 && mHasSecret) {
+ // secret keys can only be deleted individually
+ OperationResult.OperationLog log = new OperationResult.OperationLog();
+ log.add(OperationResult.LogType.MSG_DEL_ERROR_MULTI_SECRET, 0);
+ returnResult(new DeleteResult(OperationResult.RESULT_ERROR, log, 0,
+ mMasterKeyIds.length));
+ }
+
+ if (mMasterKeyIds.length == 1 && mHasSecret) {
+ // if mMasterKeyIds.length == 0 we let the DeleteOperation respond
+ try {
+ HashMap<String, Object> data = new ProviderHelper(this).getUnifiedData(
+ mMasterKeyIds[0], new String[]{
+ KeychainContract.KeyRings.USER_ID,
+ KeychainContract.KeyRings.IS_REVOKED
+ }, new int[]{
+ ProviderHelper.FIELD_TYPE_STRING,
+ ProviderHelper.FIELD_TYPE_INTEGER
+ }
+ );
+
+ String name;
+ KeyRing.UserId mainUserId = KeyRing.splitUserId(
+ (String) data.get(KeychainContract.KeyRings.USER_ID));
+ if (mainUserId.name != null) {
+ name = mainUserId.name;
+ } else {
+ name = getString(R.string.user_id_no_name);
+ }
+
+ if ((long) data.get(KeychainContract.KeyRings.IS_REVOKED) > 0) {
+ showNormalDeleteDialog();
+ } else {
+ showRevokeDeleteDialog(name);
+ }
+ } catch (ProviderHelper.NotFoundException e) {
+ Log.e(Constants.TAG,
+ "Secret key to delete not found at DeleteKeyDialogActivity for "
+ + mMasterKeyIds[0], e);
+ finish();
+ }
+ } else {
+ showNormalDeleteDialog();
+ }
+ }
+
+ private void showNormalDeleteDialog() {
+
+ DeleteKeyDialogFragment deleteKeyDialogFragment
+ = DeleteKeyDialogFragment.newInstance(mMasterKeyIds, mHasSecret);
+
+ deleteKeyDialogFragment.show(getSupportFragmentManager(), "deleteKeyDialog");
+
+ }
+
+ private void showRevokeDeleteDialog(String keyname) {
+
+ RevokeDeleteDialogFragment fragment = RevokeDeleteDialogFragment.newInstance(keyname);
+ fragment.show(getSupportFragmentManager(), "deleteRevokeDialog");
+ }
+
+ private void startRevocationOperation() {
+ mRevokeOpHelper.cryptoOperation(new CryptoInputParcel(new Date(), false));
+ }
+
+ private void startDeletionOperation() {
+ mDeleteOpHelper.cryptoOperation();
+ }
+
+ private CryptoOperationHelper.Callback<RevokeKeyringParcel, RevokeResult> getRevocationCallback() {
+
+ return new CryptoOperationHelper.Callback<RevokeKeyringParcel, RevokeResult>() {
+ @Override
+ public RevokeKeyringParcel createOperationInput() {
+ return new RevokeKeyringParcel(mMasterKeyIds[0], true,
+ getIntent().getStringExtra(EXTRA_KEYSERVER));
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(RevokeResult result) {
+ returnResult(result);
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ @Override
+ public void onCryptoOperationError(RevokeResult result) {
+ returnResult(result);
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
+ };
+ }
+
+ private CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult> getDeletionCallback() {
+
+ return new CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult>() {
+ @Override
+ public DeleteKeyringParcel createOperationInput() {
+ return new DeleteKeyringParcel(mMasterKeyIds, mHasSecret);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(DeleteResult result) {
+ returnResult(result);
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ @Override
+ public void onCryptoOperationError(DeleteResult result) {
+ returnResult(result);
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
+ };
+ }
+
+ private void returnResult(OperationResult result) {
+ Intent intent = new Intent();
+ intent.putExtra(OperationResult.EXTRA_RESULT, result);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mDeleteOpHelper.handleActivityResult(requestCode, resultCode, data);
+ mRevokeOpHelper.handleActivityResult(requestCode, resultCode, data);
+ }
+
+ public static class DeleteKeyDialogFragment extends DialogFragment {
+
+ private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids";
+ private static final String ARG_HAS_SECRET = "has_secret";
+
+ private TextView mMainMessage;
+ private View mInflateView;
+
+ /**
+ * Creates new instance of this delete file dialog fragment
+ */
+ public static DeleteKeyDialogFragment newInstance(long[] masterKeyIds, boolean hasSecret) {
+ DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
+ Bundle args = new Bundle();
+
+ args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds);
+ args.putBoolean(ARG_HAS_SECRET, hasSecret);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final FragmentActivity activity = getActivity();
+
+ final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS);
+ final boolean hasSecret = getArguments().getBoolean(ARG_HAS_SECRET);
+
+ ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity);
+
+ CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme);
+
+ // Setup custom View to display in AlertDialog
+ LayoutInflater inflater = LayoutInflater.from(theme);
+ mInflateView = inflater.inflate(R.layout.view_key_delete_fragment, null);
+ builder.setView(mInflateView);
+
+ mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage);
+
+ // If only a single key has been selected
+ if (masterKeyIds.length == 1) {
+ long masterKeyId = masterKeyIds[0];
+
+ try {
+ HashMap<String, Object> data = new ProviderHelper(activity).getUnifiedData(
+ masterKeyId, new String[]{
+ KeychainContract.KeyRings.USER_ID,
+ KeychainContract.KeyRings.HAS_ANY_SECRET
+ }, new int[]{
+ ProviderHelper.FIELD_TYPE_STRING,
+ ProviderHelper.FIELD_TYPE_INTEGER
+ }
+ );
+ String name;
+ KeyRing.UserId mainUserId = KeyRing.splitUserId((String) data.get(KeychainContract.KeyRings.USER_ID));
+ if (mainUserId.name != null) {
+ name = mainUserId.name;
+ } else {
+ name = getString(R.string.user_id_no_name);
+ }
+
+ if (hasSecret) {
+ // show title only for secret key deletions,
+ // see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior
+ builder.setTitle(getString(R.string.title_delete_secret_key, name));
+ mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name));
+ } else {
+ mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name));
+ }
+ } catch (ProviderHelper.NotFoundException e) {
+ dismiss();
+ return null;
+ }
+ } else {
+ mMainMessage.setText(R.string.key_deletion_confirmation_multi);
+ }
+
+ builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+
+ ((DeleteKeyDialogActivity) getActivity()).startDeletionOperation();
+ }
+ });
+
+ builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+
+ return builder.show();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+ getActivity().setResult(RESULT_CANCELED);
+ getActivity().finish();
+ }
+ }
+
+ public static class RevokeDeleteDialogFragment extends DialogFragment {
+
+ public static final String ARG_KEY_NAME = "arg_key_name";
+
+ public static RevokeDeleteDialogFragment newInstance(String keyName) {
+ Bundle args = new Bundle();
+ args.putString(ARG_KEY_NAME, keyName);
+ RevokeDeleteDialogFragment frag = new RevokeDeleteDialogFragment();
+ frag.setArguments(args);
+ return frag;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+
+ final String CHOICE_REVOKE = getString(R.string.del_rev_dialog_choice_rev_upload);
+ final String CHOICE_DELETE = getString(R.string.del_rev_dialog_choice_delete);
+
+ ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity);
+
+ CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme);
+ builder.setTitle(getString(R.string.del_rev_dialog_title,
+ getArguments().get(ARG_KEY_NAME)));
+
+ LayoutInflater inflater = LayoutInflater.from(theme);
+ View view = inflater.inflate(R.layout.del_rev_dialog, null);
+ builder.setView(view);
+
+ final Spinner spinner = (Spinner) view.findViewById(R.id.spinner);
+
+ builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+
+ builder.setPositiveButton(R.string.del_rev_dialog_btn_revoke,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+
+ String choice = spinner.getSelectedItem().toString();
+ if (choice.equals(CHOICE_REVOKE)) {
+ ((DeleteKeyDialogActivity) activity)
+ .startRevocationOperation();
+ } else if (choice.equals(CHOICE_DELETE)) {
+ ((DeleteKeyDialogActivity) activity)
+ .showNormalDeleteDialog();
+ } else {
+ throw new AssertionError(
+ "Unsupported delete type in RevokeDeleteDialogFragment");
+ }
+ }
+ });
+
+ final AlertDialog alertDialog = builder.show();
+
+ spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+
+ String choice = parent.getItemAtPosition(pos).toString();
+
+ if (choice.equals(CHOICE_REVOKE)) {
+ alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+ .setText(R.string.del_rev_dialog_btn_revoke);
+ } else if (choice.equals(CHOICE_DELETE)) {
+ alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+ .setText(R.string.del_rev_dialog_btn_delete);
+ } else {
+ throw new AssertionError(
+ "Unsupported delete type in RevokeDeleteDialogFragment");
+ }
+ }
+
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+
+ return alertDialog;
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+ getActivity().setResult(RESULT_CANCELED);
+ getActivity().finish();
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
index be21cdde1..3c8e972b9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
@@ -41,13 +41,7 @@ public class DisplayTextActivity extends BaseActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setFullScreenDialogClose(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- setResult(Activity.RESULT_CANCELED);
- finish();
- }
- }, false);
+ setFullScreenDialogClose(Activity.RESULT_CANCELED, false);
// Handle intent actions
handleActions(savedInstanceState, getIntent());
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java
index 7b3af48cc..dc06e9115 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java
@@ -17,6 +17,10 @@
package org.sufficientlysecure.keychain.ui;
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -29,9 +33,9 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.ShareHelper;
public class DisplayTextFragment extends DecryptFragment {
@@ -80,8 +84,19 @@ public class DisplayTextFragment extends DecryptFragment {
}
private void copyToClipboard(String text) {
- ClipboardReflection.copyToClipboard(getActivity(), text);
- Notify.create(getActivity(), R.string.text_copied_to_clipboard, Notify.Style.OK).show();
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
+ if (clipMan == null) {
+ Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show();
+ return;
+ }
+
+ clipMan.setPrimaryClip(ClipData.newPlainText(Constants.CLIPBOARD_LABEL, text));
+ Notify.create(activity, R.string.text_copied_to_clipboard, Style.OK).show();
}
@Override
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
index 1363d44f2..4769e68d8 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
@@ -57,7 +57,7 @@ import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter;
-import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
+import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment;
@@ -68,7 +68,9 @@ import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
-public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, OperationResult>
+import java.util.Date;
+
+public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyringParcel, OperationResult>
implements LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
@@ -151,7 +153,7 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel,
if (mDataUri == null) {
returnKeyringParcel();
} else {
- cryptoOperation(new CryptoInputParcel());
+ cryptoOperation(new CryptoInputParcel(new Date()));
}
}
}, new OnClickListener() {
@@ -192,7 +194,7 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel,
private void loadData(Uri dataUri) {
mDataUri = dataUri;
- Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
+ Log.i(Constants.TAG, "mDataUri: " + mDataUri);
// load the secret key ring. we do verify here that the passphrase is correct, so cached won't do
try {
@@ -437,44 +439,50 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel,
}
break;
}
- case EditSubkeyDialogFragment.MESSAGE_KEYTOCARD: {
- Activity activity = EditKeyFragment.this.getActivity();
- SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
- if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
- secretKeyType == SecretKeyType.GNU_DUMMY) {
- Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR)
- .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
- break;
- }
- int algorithm = mSubkeysAdapter.getAlgorithm(position);
- // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN
- if (algorithm != 1 && algorithm != 2 && algorithm != 3) {
- Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR)
- .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
- break;
- }
- if (mSubkeysAdapter.getKeySize(position) != 2048) {
- Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR)
- .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
- break;
- }
-
-
- SubkeyChange change;
- change = mSaveKeyringParcel.getSubkeyChange(keyId);
- if (change == null) {
- mSaveKeyringParcel.mChangeSubKeys.add(
- new SubkeyChange(keyId, false, true)
- );
- break;
- }
- // toggle
- change.mMoveKeyToCard = !change.mMoveKeyToCard;
- if (change.mMoveKeyToCard && change.mDummyStrip) {
- // User had chosen to strip key, but now wants to divert it.
- change.mDummyStrip = false;
- }
+ case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_CARD: {
+ // TODO: enable later when Admin PIN handling is resolved
+ Notify.create(getActivity(),
+ "This feature will be available in an upcoming OpenKeychain version.",
+ Notify.Style.WARN).show();
break;
+
+// Activity activity = EditKeyFragment.this.getActivity();
+// SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
+// if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
+// secretKeyType == SecretKeyType.GNU_DUMMY) {
+// Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR)
+// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
+// break;
+// }
+// int algorithm = mSubkeysAdapter.getAlgorithm(position);
+// // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN
+// if (algorithm != 1 && algorithm != 2 && algorithm != 3) {
+// Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR)
+// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
+// break;
+// }
+// if (mSubkeysAdapter.getKeySize(position) != 2048) {
+// Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR)
+// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
+// break;
+// }
+//
+//
+// SubkeyChange change;
+// change = mSaveKeyringParcel.getSubkeyChange(keyId);
+// if (change == null) {
+// mSaveKeyringParcel.mChangeSubKeys.add(
+// new SubkeyChange(keyId, false, true)
+// );
+// break;
+// }
+// // toggle
+// change.mMoveKeyToCard = !change.mMoveKeyToCard;
+// if (change.mMoveKeyToCard && change.mDummyStrip) {
+// // User had chosen to strip key, but now wants to divert it.
+// change.mDummyStrip = false;
+// }
+// break;
}
}
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
@@ -612,13 +620,16 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel,
}
@Override
- public void onCryptoOperationSuccess(OperationResult result) {
+ public void onQueuedOperationSuccess(OperationResult result) {
+
+ // null-protected from Queueing*Fragment
+ Activity activity = getActivity();
// if good -> finish, return result to showkey and display there!
Intent intent = new Intent();
intent.putExtra(OperationResult.EXTRA_RESULT, result);
- getActivity().setResult(EditKeyActivity.RESULT_OK, intent);
- getActivity().finish();
+ activity.setResult(EditKeyActivity.RESULT_OK, intent);
+ activity.finish();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java
index 779b22535..fc72a6c9f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java
@@ -124,8 +124,7 @@ public class EncryptDecryptOverviewFragment extends Fragment {
super.onResume();
// get text from clipboard
- final CharSequence clipboardText =
- ClipboardReflection.getClipboardText(getActivity());
+ final CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
// if it's null, nothing to do here /o/
if (clipboardText == null) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java
index d51b6e0ff..136787984 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.ui;
+import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -41,12 +42,7 @@ public class EncryptFilesActivity extends EncryptActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setFullScreenDialogClose(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- finish();
- }
- }, false);
+ setFullScreenDialogClose(Activity.RESULT_OK, false);
Intent intent = getIntent();
String action = intent.getAction();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
index 215af5885..3dc93872d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.ui;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -35,6 +36,7 @@ import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -56,6 +58,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.PgpConstants;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
@@ -276,18 +279,21 @@ public class EncryptFilesFragment
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.encrypt_save: {
+ hideKeyboard();
mAfterEncryptAction = AfterEncryptAction.SAVE;
- cryptoOperation();
+ cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
case R.id.encrypt_share: {
+ hideKeyboard();
mAfterEncryptAction = AfterEncryptAction.SHARE;
- cryptoOperation();
+ cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
case R.id.encrypt_copy: {
+ hideKeyboard();
mAfterEncryptAction = AfterEncryptAction.COPY;
- cryptoOperation();
+ cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
case R.id.check_use_armor: {
@@ -386,7 +392,13 @@ public class EncryptFilesFragment
}
@Override
- public void onCryptoOperationSuccess(final SignEncryptResult result) {
+ public void onQueuedOperationSuccess(final SignEncryptResult result) {
+ super.onQueuedOperationSuccess(result);
+
+ hideKeyboard();
+
+ // protected by Queueing*Fragment
+ FragmentActivity activity = getActivity();
if (mDeleteAfterEncrypt) {
// TODO make behavior coherent here
@@ -400,13 +412,18 @@ public class EncryptFilesFragment
// Share encrypted message/file
startActivity(sendWithChooserExcludingEncrypt());
} else {
+ Activity activity = getActivity();
+ if (activity == null) {
+ // it's gone, there's nothing we can do here
+ return;
+ }
// Save encrypted file
- result.createNotify(getActivity()).show();
+ result.createNotify(activity).show();
}
}
});
- deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
+ deleteFileDialog.show(activity.getSupportFragmentManager(), "deleteDialog");
} else {
switch (mAfterEncryptAction) {
@@ -417,25 +434,24 @@ public class EncryptFilesFragment
break;
case COPY:
- Activity activity = getActivity();
- if (activity == null) {
- // it's gone, there's nothing we can do here
- return;
- }
ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
+ if (clipMan == null) {
+ Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show();
+ break;
+ }
ClipData clip = new ClipData(getString(R.string.label_clip_title),
// make available as application/pgp-encrypted
new String[] { "text/plain" },
new ClipData.Item(mOutputUris.get(0))
);
clipMan.setPrimaryClip(clip);
- result.createNotify(getActivity()).show();
+ result.createNotify(activity).show();
break;
case SAVE:
// Encrypted file was saved already, just show notification
- result.createNotify(getActivity()).show();
+ result.createNotify(activity).show();
break;
}
}
@@ -652,7 +668,7 @@ public class EncryptFilesFragment
mOutputUris.add(data.getData());
// make sure this is correct at this point
mAfterEncryptAction = AfterEncryptAction.SAVE;
- cryptoOperation();
+ cryptoOperation(new CryptoInputParcel(new Date()));
}
return;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
index cb26ea452..a849cdf12 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.ui;
+import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
@@ -39,12 +40,7 @@ public class EncryptTextActivity extends EncryptActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setFullScreenDialogClose(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- finish();
- }
- }, false);
+ setFullScreenDialogClose(Activity.RESULT_OK, false);
Intent intent = getIntent();
String action = intent.getAction();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
index 886c52651..32257eba5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
@@ -18,6 +18,9 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
@@ -33,11 +36,11 @@ import android.widget.TextView;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.PgpConstants;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
@@ -46,6 +49,7 @@ import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.ShareHelper;
+import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@@ -164,13 +168,15 @@ public class EncryptTextFragment
// break;
// }
case R.id.encrypt_copy: {
+ hideKeyboard();
mShareAfterEncrypt = false;
- cryptoOperation();
+ cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
case R.id.encrypt_share: {
+ hideKeyboard();
mShareAfterEncrypt = true;
- cryptoOperation();
+ cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
default: {
@@ -265,8 +271,21 @@ public class EncryptTextFragment
return data;
}
- private void copyToClipboard(byte[] resultBytes) {
- ClipboardReflection.copyToClipboard(getActivity(), new String(resultBytes));
+ private void copyToClipboard(SignEncryptResult result) {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
+ if (clipMan == null) {
+ Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show();
+ return;
+ }
+
+ ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, new String(result.getResultBytes()));
+ clipMan.setPrimaryClip(clip);
+ result.createNotify(activity).show();
}
/**
@@ -316,18 +335,17 @@ public class EncryptTextFragment
}
@Override
- public void onCryptoOperationSuccess(SignEncryptResult result) {
+ public void onQueuedOperationSuccess(SignEncryptResult result) {
+ super.onQueuedOperationSuccess(result);
+
+ hideKeyboard();
if (mShareAfterEncrypt) {
// Share encrypted message/file
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
} else {
// Copy to clipboard
- copyToClipboard(result.getResultBytes());
- result.createNotify(getActivity()).show();
- // Notify.create(EncryptTextActivity.this,
- // R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
- // .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
+ copyToClipboard(result);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java
index ac4b94d64..acbb695df 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2012-2015 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
@@ -51,7 +51,7 @@ public class HelpAboutFragment extends Fragment {
try {
String html = new Markdown4jProcessor().process(
getActivity().getResources().openRawResource(R.raw.help_about));
- aboutTextView.setHtmlFromString(html, true);
+ aboutTextView.setHtmlFromString(html, new HtmlTextView.LocalImageGetter());
} catch (IOException e) {
Log.e(Constants.TAG, "IOException", e);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java
index 97d39feb1..73ce7e03e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2012-2015 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
@@ -17,7 +17,6 @@
package org.sufficientlysecure.keychain.ui;
-import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.TypedValue;
@@ -34,9 +33,6 @@ import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
public class HelpMarkdownFragment extends Fragment {
- private Activity mActivity;
-
- private int mHtmlFile;
public static final String ARG_MARKDOWN_RES = "htmlFile";
@@ -56,15 +52,13 @@ public class HelpMarkdownFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- mActivity = getActivity();
-
- mHtmlFile = getArguments().getInt(ARG_MARKDOWN_RES);
+ int mHtmlFile = getArguments().getInt(ARG_MARKDOWN_RES);
- ScrollView scroller = new ScrollView(mActivity);
- HtmlTextView text = new HtmlTextView(mActivity);
+ ScrollView scroller = new ScrollView(getActivity());
+ HtmlTextView text = new HtmlTextView(getActivity());
// padding
- int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, mActivity
+ int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, getActivity()
.getResources().getDisplayMetrics());
text.setPadding(padding, padding, padding, 0);
@@ -72,8 +66,9 @@ public class HelpMarkdownFragment extends Fragment {
// load markdown from raw resource
try {
- String html = new Markdown4jProcessor().process(getActivity().getResources().openRawResource(mHtmlFile));
- text.setHtmlFromString(html, true);
+ String html = new Markdown4jProcessor().process(
+ getActivity().getResources().openRawResource(mHtmlFile));
+ text.setHtmlFromString(html, new HtmlTextView.LocalImageGetter());
} catch (IOException e) {
Log.e(Constants.TAG, "IOException", e);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
index ba4f759e6..4ef6c40dc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
@@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui;
+import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -92,6 +93,7 @@ public class ImportKeysActivity extends BaseNfcActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setFullScreenDialogClose(Activity.RESULT_CANCELED, true);
mImportButton = findViewById(R.id.import_import);
mImportButton.setOnClickListener(new OnClickListener() {
@Override
@@ -224,9 +226,9 @@ public class ImportKeysActivity extends BaseNfcActivity
Notify.Style.WARN).show(mTopFragment);
// we just set the keyserver
startCloudFragment(savedInstanceState, null, false, keyserver);
- // it's not necessary to set the keyserver for ImportKeysListFragment since
- // it'll be taken care of by ImportKeysCloudFragment when the user clicks
- // the search button
+ // we don't set the keyserver for ImportKeysListFragment since
+ // it'll be set in the cloudSearchPrefs of ImportKeysCloudFragment
+ // which is used when the user clicks on the search button
startListFragment(savedInstanceState, null, null, null, null);
} else {
// we allow our users to edit the query if they wish
@@ -316,7 +318,8 @@ public class ImportKeysActivity extends BaseNfcActivity
* specified in user preferences
*/
- private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String keyserver) {
+ private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String
+ keyserver) {
// 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.
@@ -346,7 +349,7 @@ public class ImportKeysActivity extends BaseNfcActivity
}
}
- public void loadCallback(ImportKeysListFragment.LoaderState loaderState) {
+ public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) {
mListFragment.loadNew(loaderState);
}
@@ -395,7 +398,7 @@ public class ImportKeysActivity extends BaseNfcActivity
}
mOperationHelper = new CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult>(
- this, this, R.string.progress_importing
+ 1, this, this, R.string.progress_importing
);
ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState();
@@ -448,10 +451,8 @@ public class ImportKeysActivity extends BaseNfcActivity
}
@Override
- protected void onNfcPerform() throws IOException {
- // this displays the key or moves to the yubikey import dialogue.
- super.onNfcPerform();
- // either way, finish afterwards
+ protected void onNfcPostExecute() throws IOException {
+ // either way, finish after NFC AsyncTask
finish();
}
@@ -463,7 +464,7 @@ public class ImportKeysActivity extends BaseNfcActivity
}
}
- public void handleResult (ImportKeyResult result) {
+ public void handleResult(ImportKeyResult result) {
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction())
|| ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) {
Intent intent = new Intent();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
index bf7e41045..53e5efabe 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
@@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListCloudLoader;
@@ -41,7 +43,9 @@ import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
+import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
@@ -62,6 +66,7 @@ public class ImportKeysListFragment extends ListFragment implements
private Activity mActivity;
private ImportKeysAdapter mAdapter;
+ private ParcelableProxy mParcelableProxy;
private LoaderState mLoaderState;
@@ -71,6 +76,8 @@ public class ImportKeysListFragment extends ListFragment implements
private LongSparseArray<ParcelableKeyRing> mCachedKeyData;
private boolean mNonInteractive;
+ private boolean mShowingOrbotDialog;
+
public LoaderState getLoaderState() {
return mLoaderState;
}
@@ -126,6 +133,7 @@ public class ImportKeysListFragment extends ListFragment implements
/**
* Creates an interactive ImportKeyListFragment which reads keyrings from bytes, or file specified
* by dataUri, or searches a keyserver for serverQuery, if parameter is not null, in that order
+ * Will immediately load data if non-null bytes/dataUri/serverQuery
*
* @param bytes byte data containing list of keyrings to be imported
* @param dataUri file from which keyrings are to be imported
@@ -141,7 +149,7 @@ public class ImportKeysListFragment extends ListFragment implements
/**
* Visually consists of a list of keyrings with checkboxes to specify which are to be imported
- * Can immediately load keyrings specified by any of its parameters
+ * Will immediately load data if non-null bytes/dataUri/serverQuery is supplied
*
* @param bytes byte data containing list of keyrings to be imported
* @param dataUri file from which keyrings are to be imported
@@ -259,6 +267,7 @@ public class ImportKeysListFragment extends ListFragment implements
}
public void loadNew(LoaderState loaderState) {
+
mLoaderState = loaderState;
restartLoaders();
@@ -301,7 +310,8 @@ public class ImportKeysListFragment extends ListFragment implements
}
case LOADER_ID_CLOUD: {
CloudLoaderState ls = (CloudLoaderState) mLoaderState;
- return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs);
+ return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs,
+ mParcelableProxy);
}
default:
@@ -349,6 +359,46 @@ public class ImportKeysListFragment extends ListFragment implements
if (getKeyResult.success()) {
// No error
+ } else if (getKeyResult.isPending()) {
+ if (getKeyResult.getRequiredInputParcel().mType ==
+ RequiredInputParcel.RequiredInputType.ENABLE_ORBOT) {
+ if (mShowingOrbotDialog) {
+ // to prevent dialogs stacking
+ return;
+ }
+
+ // this is because we can't commit fragment dialogs in onLoadFinished
+ Runnable showOrbotDialog = new Runnable() {
+ @Override
+ public void run() {
+ final Runnable ignoreTor = new Runnable() {
+ @Override
+ public void run() {
+ mParcelableProxy = ParcelableProxy
+ .getForNoProxy();
+ mShowingOrbotDialog = false;
+ restartLoaders();
+ }
+ };
+
+ final Runnable dialogDismiss = new Runnable() {
+ @Override
+ public void run() {
+ mShowingOrbotDialog = false;
+ }
+ };
+
+ if (OrbotHelper.putOrbotInRequiredState(
+ ignoreTor, dialogDismiss, getActivity())) {
+ // looks like we didn't have to show the
+ // dialog after all
+ restartLoaders();
+ }
+ }
+ };
+ new Handler().post(showOrbotDialog );
+ mShowingOrbotDialog = true;
+ }
} else {
getKeyResult.createNotify(getActivity()).show();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
index da713d0d8..b60f3984c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
@@ -110,7 +110,16 @@ public class ImportKeysProxyActivity extends FragmentActivity
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mImportOpHelper != null) {
- mImportOpHelper.cryptoOperation();
+ if (!mImportOpHelper.handleActivityResult(requestCode, resultCode, data)) {
+ // if a result has been returned, and it does not belong to mImportOpHelper,
+ // return it down to other activity
+ if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
+ returnResult(data);
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ finish();
+ }
+ }
}
if (requestCode == IntentIntegratorSupportV4.REQUEST_CODE) {
@@ -128,13 +137,6 @@ public class ImportKeysProxyActivity extends FragmentActivity
return;
}
- // if a result has been returned, return it down to other activity
- if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
- returnResult(data);
- } else {
- super.onActivityResult(requestCode, resultCode, data);
- finish();
- }
}
private void processScannedContent(String content) {
@@ -157,8 +159,7 @@ public class ImportKeysProxyActivity extends FragmentActivity
returnResult(intent);
return;
}
-
- String fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH);
+ final String fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH);
if (!fingerprint.matches("[a-fA-F0-9]{40}")) {
SingletonResult result = new SingletonResult(
SingletonResult.RESULT_ERROR, LogType.MSG_WRONG_QR_CODE_FP);
@@ -222,7 +223,7 @@ public class ImportKeysProxyActivity extends FragmentActivity
mKeyList = keyRings;
- mImportOpHelper = new CryptoOperationHelper<>(this, this, R.string.progress_importing);
+ mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_importing);
mImportOpHelper.cryptoOperation();
}
@@ -256,7 +257,6 @@ public class ImportKeysProxyActivity extends FragmentActivity
Intent data = new Intent();
data.putExtras(returnData);
returnResult(data);
- return;
}
@Override
@@ -273,7 +273,8 @@ public class ImportKeysProxyActivity extends FragmentActivity
// only one message sent during the beam
NdefMessage msg = (NdefMessage) rawMsgs[0];
// record 0 contains the MIME type, record 1 is the AAR, if present
- byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload();
+ final byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload();
+
importKeys(receivedKeyringBytes);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
index e038cf948..0488d8235 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
@@ -18,6 +18,11 @@
package org.sufficientlysecure.keychain.ui;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
@@ -28,9 +33,6 @@ import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Messenger;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
@@ -51,12 +53,10 @@ import android.widget.TextView;
import com.getbase.floatingactionbutton.FloatingActionButton;
import com.getbase.floatingactionbutton.FloatingActionsMenu;
-
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
-import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.KeychainContract;
@@ -65,22 +65,15 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
-import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
-import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
-import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
+import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
-import org.sufficientlysecure.keychain.util.ExportHelper;
import org.sufficientlysecure.keychain.util.FabContainer;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
@@ -93,10 +86,9 @@ public class KeyListFragment extends LoaderFragment
LoaderManager.LoaderCallbacks<Cursor>, FabContainer,
CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
- static final int REQUEST_REPEAT_PASSPHRASE = 1;
- static final int REQUEST_ACTION = 2;
-
- ExportHelper mExportHelper;
+ static final int REQUEST_ACTION = 1;
+ private static final int REQUEST_DELETE = 2;
+ private static final int REQUEST_VIEW_KEY = 3;
private KeyListAdapter mAdapter;
private StickyListHeadersListView mStickyList;
@@ -116,18 +108,6 @@ public class KeyListFragment extends LoaderFragment
// for ConsolidateOperation
private CryptoOperationHelper<ConsolidateInputParcel, ConsolidateResult> mConsolidateOpHelper;
- // This ids for multiple key export.
- private ArrayList<Long> mIdsForRepeatAskPassphrase;
- // This index for remembering the number of master key.
- private int mIndex;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mExportHelper = new ExportHelper(getActivity());
- }
-
/**
* Load custom layout with StickyListView from library
*/
@@ -238,19 +218,7 @@ public class KeyListFragment extends LoaderFragment
}
case R.id.menu_key_list_multi_delete: {
ids = mAdapter.getCurrentSelectedMasterKeyIds();
- showDeleteKeyDialog(mode, ids, mAdapter.isAnySecretSelected());
- break;
- }
- case R.id.menu_key_list_multi_export: {
- ids = mAdapter.getCurrentSelectedMasterKeyIds();
- showMultiExportDialog(ids);
- break;
- }
- case R.id.menu_key_list_multi_select_all: {
- // select all
- for (int i = 0; i < mAdapter.getCount(); i++) {
- mStickyList.setItemChecked(i, true);
- }
+ showDeleteKeyDialog(ids, mAdapter.isAnySecretSelected());
break;
}
}
@@ -366,7 +334,7 @@ public class KeyListFragment extends LoaderFragment
Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class);
viewIntent.setData(
KeyRings.buildGenericKeyRingUri(mAdapter.getMasterKeyId(position)));
- startActivity(viewIntent);
+ startActivityForResult(viewIntent, REQUEST_VIEW_KEY);
}
protected void encrypt(ActionMode mode, long[] masterKeyIds) {
@@ -384,38 +352,15 @@ public class KeyListFragment extends LoaderFragment
*
* @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not
*/
- public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) {
- // Can only work on singular secret keys
- if (hasSecret && masterKeyIds.length > 1) {
- Notify.create(getActivity(), R.string.secret_cannot_multiple,
- Notify.Style.ERROR).show();
- return;
+ public void showDeleteKeyDialog(long[] masterKeyIds, boolean hasSecret) {
+ Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class);
+ intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, masterKeyIds);
+ intent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, hasSecret);
+ if (hasSecret) {
+ intent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER,
+ Preferences.getPreferences(getActivity()).getPreferredKeyserver());
}
-
- // Message is received after key is deleted
- Handler returnHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- if (message.arg1 == DeleteKeyDialogFragment.MESSAGE_OKAY) {
- Bundle data = message.getData();
- if (data != null) {
- DeleteResult result = data.getParcelable(DeleteResult.EXTRA_RESULT);
- if (result != null) {
- result.createNotify(getActivity()).show();
- }
- }
- mode.finish();
- }
- }
- };
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(returnHandler);
-
- DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
- masterKeyIds);
-
- deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
+ startActivityForResult(intent, REQUEST_DELETE);
}
@@ -470,18 +415,10 @@ public class KeyListFragment extends LoaderFragment
createKey();
return true;
- case R.id.menu_key_list_export:
- mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
- return true;
-
case R.id.menu_key_list_update_all_keys:
updateAllKeys();
return true;
- case R.id.menu_key_list_debug_cons:
- consolidate();
- return true;
-
case R.id.menu_key_list_debug_read:
try {
KeychainDatabase.debugBackup(getActivity(), true);
@@ -512,6 +449,10 @@ public class KeyListFragment extends LoaderFragment
getActivity().finish();
return true;
+ case R.id.menu_key_list_debug_cons:
+ consolidate();
+ return true;
+
default:
return super.onOptionsItemSelected(item);
}
@@ -602,7 +543,7 @@ public class KeyListFragment extends LoaderFragment
mKeyserver = cloudPrefs.keyserver;
}
- mImportOpHelper = new CryptoOperationHelper<>(this,
+ mImportOpHelper = new CryptoOperationHelper<>(1, this,
this, R.string.progress_updating);
mImportOpHelper.cryptoOperation();
}
@@ -639,45 +580,11 @@ public class KeyListFragment extends LoaderFragment
};
mConsolidateOpHelper =
- new CryptoOperationHelper<>(this, callback, R.string.progress_importing);
+ new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing);
mConsolidateOpHelper.cryptoOperation();
}
- private void showMultiExportDialog(long[] masterKeyIds) {
- mIdsForRepeatAskPassphrase = new ArrayList<>();
- for (long id : masterKeyIds) {
- try {
- if (PassphraseCacheService.getCachedPassphrase(
- getActivity(), id, id) == null) {
- mIdsForRepeatAskPassphrase.add(id);
- }
- } catch (PassphraseCacheService.KeyNotFoundException e) {
- // This happens when the master key is stripped
- // and ignore this key.
- }
- }
- mIndex = 0;
- if (mIdsForRepeatAskPassphrase.size() != 0) {
- startPassphraseActivity();
- return;
- }
- long[] idsForMultiExport = new long[mIdsForRepeatAskPassphrase.size()];
- for (int i = 0; i < mIdsForRepeatAskPassphrase.size(); ++i) {
- idsForMultiExport[i] = mIdsForRepeatAskPassphrase.get(i);
- }
- mExportHelper.showExportKeysDialog(idsForMultiExport,
- Constants.Path.APP_DIR_FILE,
- mAdapter.isAnySecretSelected());
- }
-
- private void startPassphraseActivity() {
- Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
- long masterKeyId = mIdsForRepeatAskPassphrase.get(mIndex++);
- intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, masterKeyId);
- startActivityForResult(intent, REQUEST_REPEAT_PASSPHRASE);
- }
-
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mImportOpHelper != null) {
@@ -688,31 +595,37 @@ public class KeyListFragment extends LoaderFragment
mConsolidateOpHelper.handleActivityResult(requestCode, resultCode, data);
}
- if (requestCode == REQUEST_REPEAT_PASSPHRASE) {
- if (resultCode != Activity.RESULT_OK) {
- return;
- }
- if (mIndex < mIdsForRepeatAskPassphrase.size()) {
- startPassphraseActivity();
- return;
- }
- long[] idsForMultiExport = new long[mIdsForRepeatAskPassphrase.size()];
- for (int i = 0; i < mIdsForRepeatAskPassphrase.size(); ++i) {
- idsForMultiExport[i] = mIdsForRepeatAskPassphrase.get(i);
- }
- mExportHelper.showExportKeysDialog(idsForMultiExport,
- Constants.Path.APP_DIR_FILE,
- mAdapter.isAnySecretSelected());
- }
+ switch (requestCode) {
+ case REQUEST_DELETE:
+ if (mActionMode != null) {
+ mActionMode.finish();
+ }
+ if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
+ OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
+ result.createNotify(getActivity()).show();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ break;
- if (requestCode == REQUEST_ACTION) {
- // if a result has been returned, display a notify
- if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
- OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
- result.createNotify(getActivity()).show();
- } else {
- super.onActivityResult(requestCode, resultCode, data);
- }
+ case REQUEST_ACTION:
+ // if a result has been returned, display a notify
+ if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
+ OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
+ result.createNotify(getActivity()).show();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ break;
+
+ case REQUEST_VIEW_KEY:
+ if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
+ OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
+ result.createNotify(getActivity()).show();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ break;
}
}
@@ -763,8 +676,11 @@ public class KeyListFragment extends LoaderFragment
private HashMap<Integer, Boolean> mSelection = new HashMap<>();
+ private Context mContext;
+
public KeyListAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
+ mContext = context;
}
@Override
@@ -793,9 +709,11 @@ public class KeyListFragment extends LoaderFragment
// let the adapter handle setting up the row views
View v = super.getView(position, convertView, parent);
+ int colorEmphasis = FormattingUtils.getColorFromAttr(mContext, R.attr.colorEmphasis);
+
if (mSelection.get(position) != null) {
// selected position color
- v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis));
+ v.setBackgroundColor(colorEmphasis);
} else {
// default color
v.setBackgroundColor(Color.TRANSPARENT);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java
index 3c8fbf8c9..1cd1a3099 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java
@@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel;
import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel;
+import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Log;
@@ -57,10 +58,12 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
OperationResult mResult;
public static final String EXTRA_RESULT = "log";
+ protected int mTextColor;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mTextColor = FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorText);
setHasOptionsMenu(true);
}
@@ -357,13 +360,13 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
ih.mSecondText.setText(getResources().getString(subEntry.mType.getMsgId(),
subEntry.mParameters));
}
- ih.mSecondText.setTextColor(subEntry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK);
+ ih.mSecondText.setTextColor(subEntry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : mTextColor);
switch (subEntry.mType.mLevel) {
case DEBUG: ih.mSecondImg.setBackgroundColor(Color.GRAY); break;
- case INFO: ih.mSecondImg.setBackgroundColor(Color.BLACK); break;
+ case INFO: ih.mSecondImg.setBackgroundColor(mTextColor); break;
case WARN: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break;
case ERROR: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
- case START: ih.mSecondImg.setBackgroundColor(Color.BLACK); break;
+ case START: ih.mSecondImg.setBackgroundColor(mTextColor); break;
case OK: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break;
case CANCELLED: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
}
@@ -388,13 +391,13 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
entry.mParameters));
}
convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0);
- ih.mText.setTextColor(entry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK);
+ ih.mText.setTextColor(entry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : mTextColor);
switch (entry.mType.mLevel) {
case DEBUG: ih.mImg.setBackgroundColor(Color.GRAY); break;
- case INFO: ih.mImg.setBackgroundColor(Color.BLACK); break;
+ case INFO: ih.mImg.setBackgroundColor(mTextColor); break;
case WARN: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break;
case ERROR: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
- case START: ih.mImg.setBackgroundColor(Color.BLACK); break;
+ case START: ih.mImg.setBackgroundColor(mTextColor); break;
case OK: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break;
case CANCELLED: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
index ec6fd1bbe..6f5d98afd 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
@@ -33,6 +33,7 @@ import com.mikepenz.community_material_typeface_library.CommunityMaterial;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.typeface.FontAwesome;
import com.mikepenz.materialdrawer.Drawer;
+import com.mikepenz.materialdrawer.DrawerBuilder;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
@@ -48,14 +49,15 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
static final int ID_KEYS = 1;
static final int ID_ENCRYPT_DECRYPT = 2;
static final int ID_APPS = 3;
- static final int ID_SETTINGS = 4;
- static final int ID_HELP = 5;
+ static final int ID_BACKUP = 4;
+ static final int ID_SETTINGS = 5;
+ static final int ID_HELP = 6;
// both of these are used for instrumentation testing only
public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time";
public static final String EXTRA_INIT_FRAG = "init_frag";
- public Drawer.Result mDrawerResult;
+ public Drawer mDrawer;
private Toolbar mToolbar;
@Override
@@ -67,7 +69,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
mToolbar.setTitle(R.string.app_name);
setSupportActionBar(mToolbar);
- mDrawerResult = new Drawer()
+ mDrawer = new DrawerBuilder()
.withActivity(this)
.withHeader(R.layout.main_drawer_header)
.withToolbar(mToolbar)
@@ -77,7 +79,9 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
new PrimaryDrawerItem().withName(R.string.nav_encrypt_decrypt).withIcon(FontAwesome.Icon.faw_lock)
.withIdentifier(ID_ENCRYPT_DECRYPT).withCheckable(false),
new PrimaryDrawerItem().withName(R.string.title_api_registered_apps).withIcon(CommunityMaterial.Icon.cmd_apps)
- .withIdentifier(ID_APPS).withCheckable(false)
+ .withIdentifier(ID_APPS).withCheckable(false),
+ new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore)
+ .withIdentifier(ID_BACKUP).withCheckable(false)
)
.addStickyDrawerItems(
// display and stick on bottom of drawer
@@ -86,7 +90,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) {
+ public boolean onItemClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) {
if (drawerItem != null) {
Intent intent = null;
switch(drawerItem.getIdentifier()) {
@@ -99,6 +103,9 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
case ID_APPS:
onAppsSelected();
break;
+ case ID_BACKUP:
+ onBackupSelected();
+ break;
case ID_SETTINGS:
intent = new Intent(MainActivity.this, SettingsActivity.class);
break;
@@ -110,6 +117,8 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
MainActivity.this.startActivity(intent);
}
}
+
+ return false;
}
})
.withSelectedItem(-1)
@@ -128,6 +137,11 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
getSupportFragmentManager().addOnBackStackChangedListener(this);
+ // all further initialization steps are saved as instance state
+ if (savedInstanceState != null) {
+ return;
+ }
+
Intent data = getIntent();
// If we got an EXTRA_RESULT in the intent, show the notification
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
@@ -135,20 +149,18 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
result.createNotify(this).show();
}
- if (savedInstanceState == null) {
- // always initialize keys fragment to the bottom of the backstack
- onKeysSelected();
-
- if (data != null && data.hasExtra(EXTRA_INIT_FRAG)) {
- // initialize FragmentLayout with KeyListFragment at first
- switch (data.getIntExtra(EXTRA_INIT_FRAG, -1)) {
- case ID_ENCRYPT_DECRYPT:
- onEnDecryptSelected();
- break;
- case ID_APPS:
- onAppsSelected();
- break;
- }
+ // always initialize keys fragment to the bottom of the backstack
+ onKeysSelected();
+
+ if (data != null && data.hasExtra(EXTRA_INIT_FRAG)) {
+ // initialize FragmentLayout with KeyListFragment at first
+ switch (data.getIntExtra(EXTRA_INIT_FRAG, -1)) {
+ case ID_ENCRYPT_DECRYPT:
+ onEnDecryptSelected();
+ break;
+ case ID_APPS:
+ onAppsSelected();
+ break;
}
}
@@ -170,37 +182,44 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
private void onKeysSelected() {
mToolbar.setTitle(R.string.app_name);
- mDrawerResult.setSelectionByIdentifier(ID_KEYS, false);
+ mDrawer.setSelectionByIdentifier(ID_KEYS, false);
Fragment frag = new KeyListFragment();
setFragment(frag, false);
}
private void onEnDecryptSelected() {
mToolbar.setTitle(R.string.nav_encrypt_decrypt);
- mDrawerResult.setSelectionByIdentifier(ID_ENCRYPT_DECRYPT, false);
+ mDrawer.setSelectionByIdentifier(ID_ENCRYPT_DECRYPT, false);
Fragment frag = new EncryptDecryptOverviewFragment();
setFragment(frag, true);
}
private void onAppsSelected() {
mToolbar.setTitle(R.string.nav_apps);
- mDrawerResult.setSelectionByIdentifier(ID_APPS, false);
+ mDrawer.setSelectionByIdentifier(ID_APPS, false);
Fragment frag = new AppsListFragment();
setFragment(frag, true);
}
+ private void onBackupSelected() {
+ mToolbar.setTitle(R.string.nav_backup);
+ mDrawer.setSelectionByIdentifier(ID_APPS, false);
+ Fragment frag = new BackupFragment();
+ setFragment(frag, true);
+ }
+
@Override
protected void onSaveInstanceState(Bundle outState) {
// add the values which need to be saved from the drawer to the bundle
- outState = mDrawerResult.saveInstanceState(outState);
+ outState = mDrawer.saveInstanceState(outState);
super.onSaveInstanceState(outState);
}
@Override
public void onBackPressed() {
// close the drawer first and if the drawer is closed do regular backstack handling
- if (mDrawerResult != null && mDrawerResult.isDrawerOpen()) {
- mDrawerResult.closeDrawer();
+ if (mDrawer != null && mDrawer.isDrawerOpen()) {
+ mDrawer.closeDrawer();
} else {
super.onBackPressed();
}
@@ -238,11 +257,17 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
// make sure the selected icon is the one shown at this point
if (frag instanceof KeyListFragment) {
- mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_KEYS), false);
+ mToolbar.setTitle(R.string.app_name);
+ mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_KEYS), false);
} else if (frag instanceof EncryptDecryptOverviewFragment) {
- mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false);
+ mToolbar.setTitle(R.string.nav_encrypt_decrypt);
+ mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false);
} else if (frag instanceof AppsListFragment) {
- mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_APPS), false);
+ mToolbar.setTitle(R.string.nav_apps);
+ mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_APPS), false);
+ } else if (frag instanceof BackupFragment) {
+ mToolbar.setTitle(R.string.nav_backup);
+ mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_BACKUP), false);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java
index addfb6a23..1584b87ae 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java
@@ -7,8 +7,13 @@
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.view.View;
import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.ViewAnimator;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -28,27 +33,37 @@ import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
+import java.util.Date;
/**
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
* NFC devices.
* <p/>
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
+ * NOTE: If no CryptoInputParcel is passed via EXTRA_CRYPTO_INPUT, the CryptoInputParcel is created
+ * internally and is NOT meant to be used by signing operations before adding signature time
*/
public class NfcOperationActivity extends BaseNfcActivity {
public static final String EXTRA_REQUIRED_INPUT = "required_input";
+ public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
// passthrough for OpenPgpService
public static final String EXTRA_SERVICE_INTENT = "data";
- public static final String RESULT_DATA = "result_data";
+ public static final String RESULT_CRYPTO_INPUT = "result_data";
+
+ public ViewAnimator vAnimator;
+ public TextView vErrorText;
+ public Button vErrorTryAgainButton;
private RequiredInputParcel mRequiredInput;
private Intent mServiceIntent;
private static final byte[] BLANK_FINGERPRINT = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ private CryptoInputParcel mInputParcel;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -56,6 +71,34 @@ public class NfcOperationActivity extends BaseNfcActivity {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
+
+ if (mInputParcel == null) {
+ // for compatibility when used from OpenPgpService
+ // (or any place other than CryptoOperationHelper)
+ // NOTE: This CryptoInputParcel cannot be used for signing without adding signature time
+ mInputParcel = new CryptoInputParcel();
+ }
+
+ setTitle(R.string.nfc_text);
+
+ vAnimator = (ViewAnimator) findViewById(R.id.view_animator);
+ vAnimator.setDisplayedChild(0);
+ vErrorText = (TextView) findViewById(R.id.nfc_activity_3_error_text);
+ vErrorTryAgainButton = (Button) findViewById(R.id.nfc_activity_3_error_try_again);
+ vErrorTryAgainButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ resumeTagHandling();
+
+ // obtain passphrase for this subkey
+ if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD) {
+ obtainYubiKeyPin(mRequiredInput);
+ }
+ vAnimator.setDisplayedChild(0);
+ }
+ });
+
Intent intent = getIntent();
Bundle data = intent.getExtras();
@@ -70,33 +113,44 @@ public class NfcOperationActivity extends BaseNfcActivity {
@Override
protected void initLayout() {
- setContentView(R.layout.nfc_activity);
+ setContentView(R.layout.nfc_operation_activity);
}
@Override
- protected void onNfcPerform() throws IOException {
+ public void onNfcPreExecute() {
+ // start with indeterminate progress
+ vAnimator.setDisplayedChild(1);
+ }
- CryptoInputParcel inputParcel = new CryptoInputParcel(mRequiredInput.mSignatureTime);
+ @Override
+ protected void doNfcInBackground() throws IOException {
switch (mRequiredInput.mType) {
case NFC_DECRYPT: {
- for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) {
- byte[] hash = mRequiredInput.mInputHashes[i];
- byte[] decryptedSessionKey = nfcDecryptSessionKey(hash);
- inputParcel.addCryptoData(hash, decryptedSessionKey);
+ for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
+ byte[] encryptedSessionKey = mRequiredInput.mInputData[i];
+ byte[] decryptedSessionKey = nfcDecryptSessionKey(encryptedSessionKey);
+ mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey);
}
break;
}
case NFC_SIGN: {
- for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) {
- byte[] hash = mRequiredInput.mInputHashes[i];
+ if (mInputParcel.getSignatureTime() == null) {
+ mInputParcel.addSignatureTime(new Date());
+ }
+ for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
+ byte[] hash = mRequiredInput.mInputData[i];
int algo = mRequiredInput.mSignAlgos[i];
byte[] signedHash = nfcCalculateSignature(hash, algo);
- inputParcel.addCryptoData(hash, signedHash);
+ mInputParcel.addCryptoData(hash, signedHash);
}
break;
}
case NFC_MOVE_KEY_TO_CARD: {
+ // TODO: assume PIN and Admin PIN to be default for this operation
+ mPin = new Passphrase("123456");
+ mAdminPin = new Passphrase("12345678");
+
ProviderHelper providerHelper = new ProviderHelper(this);
CanonicalizedSecretKeyRing secretKeyRing;
try {
@@ -107,8 +161,11 @@ public class NfcOperationActivity extends BaseNfcActivity {
throw new IOException("Couldn't find subkey for key to card operation.");
}
- for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) {
- byte[] subkeyBytes = mRequiredInput.mInputHashes[i];
+ byte[] newPin = mRequiredInput.mInputData[0];
+ byte[] newAdminPin = mRequiredInput.mInputData[1];
+
+ for (int i = 2; i < mRequiredInput.mInputData.length; i++) {
+ byte[] subkeyBytes = mRequiredInput.mInputData[i];
ByteBuffer buf = ByteBuffer.wrap(subkeyBytes);
long subkeyId = buf.getLong();
@@ -155,21 +212,71 @@ public class NfcOperationActivity extends BaseNfcActivity {
throw new IOException("Inappropriate key flags for smart card key.");
}
- inputParcel.addCryptoData(subkeyBytes, cardSerialNumber);
+ // TODO: Is this really needed?
+ mInputParcel.addCryptoData(subkeyBytes, cardSerialNumber);
}
+
+ // change PINs afterwards
+ nfcModifyPIN(0x81, newPin);
+ nfcModifyPIN(0x83, newAdminPin);
+
+ break;
+ }
+ default: {
+ throw new AssertionError("Unhandled mRequiredInput.mType");
}
}
+ }
+
+ @Override
+ protected void onNfcPostExecute() throws IOException {
if (mServiceIntent != null) {
- CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, inputParcel);
+ // if we're triggered by OpenPgpService
+ CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, mInputParcel);
+ mServiceIntent.putExtra(EXTRA_CRYPTO_INPUT,
+ getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT));
setResult(RESULT_OK, mServiceIntent);
} else {
Intent result = new Intent();
- result.putExtra(NfcOperationActivity.RESULT_DATA, inputParcel);
+ result.putExtra(RESULT_CRYPTO_INPUT, mInputParcel);
+ // send back the CryptoInputParcel we receive, to conform with the pattern in
+ // CryptoOperationHelper
setResult(RESULT_OK, result);
}
- finish();
+ // show finish
+ vAnimator.setDisplayedChild(2);
+
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ // check all 200ms if YubiKey has been taken away
+ while (true) {
+ if (isNfcConnected()) {
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ignored) {
+ }
+ } else {
+ return null;
+ }
+ }
+ }
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ finish();
+ }
+ }.execute();
+ }
+
+ @Override
+ protected void onNfcError(String error) {
+ pauseTagHandling();
+
+ vErrorText.setText(error + "\n\n" + getString(R.string.nfc_try_again_text));
+ vAnimator.setDisplayedChild(3);
}
private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException {
@@ -199,8 +306,6 @@ public class NfcOperationActivity extends BaseNfcActivity {
// clear (invalid) passphrase
PassphraseCacheService.clearCachedPassphrase(
this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
-
- obtainYubiKeyPin(mRequiredInput);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java
new file mode 100644
index 000000000..587044659
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.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.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.util.ParcelableProxy;
+import org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
+
+/**
+ * Simply encapsulates a dialog. If orbot is not installed, it shows an install dialog, else a
+ * dialog to enable orbot.
+ */
+public class OrbotRequiredDialogActivity extends FragmentActivity {
+
+ // to provide any previous crypto input into which proxy preference is merged
+ public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input";
+
+ public static final String RESULT_CRYPTO_INPUT = "result_crypto_input";
+
+ private CryptoInputParcel mCryptoInputParcel;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
+ if (mCryptoInputParcel == null) {
+ mCryptoInputParcel = new CryptoInputParcel();
+ }
+ showDialog();
+ }
+
+ /**
+ * Displays an install or start orbot dialog depending on orbot's presence and state
+ */
+ public void showDialog() {
+ DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
+ public void run() {
+ Runnable ignoreTor = new Runnable() {
+ @Override
+ public void run() {
+ Intent intent = new Intent();
+ mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy());
+ intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ };
+
+ Runnable dialogDismissed = new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ };
+
+ if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, dialogDismissed,
+ Preferences.getPreferences(OrbotRequiredDialogActivity.this)
+ .getProxyPrefs(),
+ OrbotRequiredDialogActivity.this)) {
+ // no action required after all
+ Intent intent = new Intent();
+ intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
+ setResult(RESULT_OK, intent);
+ }
+ }
+ });
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
index bb12cd7ff..d7224bd04 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
@@ -19,15 +19,16 @@ package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
-import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
+import android.support.v7.app.AlertDialog;
import android.text.InputType;
import android.text.method.PasswordTransformationMethod;
import android.view.ContextThemeWrapper;
@@ -58,6 +59,7 @@ import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
@@ -65,17 +67,22 @@ import org.sufficientlysecure.keychain.util.Preferences;
/**
* We can not directly create a dialog on the application context.
* This activity encapsulates a DialogFragment to emulate a dialog.
+ * NOTE: If no CryptoInputParcel is passed via EXTRA_CRYPTO_INPUT, the CryptoInputParcel is created
+ * internally and is NOT meant to be used by signing operations before adding a signature time
*/
public class PassphraseDialogActivity extends FragmentActivity {
public static final String RESULT_CRYPTO_INPUT = "result_data";
public static final String EXTRA_REQUIRED_INPUT = "required_input";
public static final String EXTRA_SUBKEY_ID = "secret_key_id";
+ public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
// special extra for OpenPgpService
public static final String EXTRA_SERVICE_INTENT = "data";
private long mSubKeyId;
+ private CryptoInputParcel mCryptoInputParcel;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -89,6 +96,15 @@ public class PassphraseDialogActivity extends FragmentActivity {
);
}
+ mCryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
+
+ if (mCryptoInputParcel == null) {
+ // not all usages of PassphraseActivity are from CryptoInputOperation
+ // NOTE: This CryptoInputParcel cannot be used for signing operations without setting
+ // signature time
+ mCryptoInputParcel = new CryptoInputParcel();
+ }
+
// this activity itself has no content view (see manifest)
if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) {
@@ -112,7 +128,8 @@ public class PassphraseDialogActivity extends FragmentActivity {
SecretKeyType.PASSPHRASE_EMPTY) {
// also return passphrase back to activity
Intent returnIntent = new Intent();
- returnIntent.putExtra(RESULT_CRYPTO_INPUT, new CryptoInputParcel(new Passphrase("")));
+ mCryptoInputParcel.mPassphrase = new Passphrase("");
+ returnIntent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
setResult(RESULT_OK, returnIntent);
finish();
return;
@@ -136,8 +153,8 @@ public class PassphraseDialogActivity extends FragmentActivity {
}
@Override
- protected void onResume() {
- super.onResume();
+ protected void onResumeFragments() {
+ super.onResumeFragments();
/* Show passphrase dialog to cache a new passphrase the user enters for using it later for
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
@@ -175,14 +192,12 @@ public class PassphraseDialogActivity extends FragmentActivity {
private Intent mServiceIntent;
+ @NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
- // if the dialog is displayed from the application class, design is missing
- // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay
- ContextThemeWrapper theme = new ContextThemeWrapper(activity,
- R.style.Theme_AppCompat_Light_Dialog);
+ ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity);
mSubKeyId = getArguments().getLong(EXTRA_SUBKEY_ID);
mServiceIntent = getArguments().getParcelable(EXTRA_SERVICE_INTENT);
@@ -299,10 +314,10 @@ public class PassphraseDialogActivity extends FragmentActivity {
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
mPassphraseEditText.setOnEditorActionListener(this);
- if (keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) {
- mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD);
- } else if (keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
- mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin())
+ || keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
+ mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
+ mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
} else {
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
}
@@ -328,11 +343,16 @@ public class PassphraseDialogActivity extends FragmentActivity {
public void onClick(View v) {
final Passphrase passphrase = new Passphrase(mPassphraseEditText);
+ CryptoInputParcel cryptoInputParcel =
+ ((PassphraseDialogActivity) getActivity()).mCryptoInputParcel;
+
// Early breakout if we are dealing with a symmetric key
if (mSecretRing == null) {
- PassphraseCacheService.addCachedPassphrase(getActivity(),
- Constants.key.symmetric, Constants.key.symmetric, passphrase,
- getString(R.string.passp_cache_notif_pwd));
+ if (cryptoInputParcel.mCachePassphrase) {
+ PassphraseCacheService.addCachedPassphrase(getActivity(),
+ Constants.key.symmetric, Constants.key.symmetric, passphrase,
+ getString(R.string.passp_cache_notif_pwd));
+ }
finishCaching(passphrase);
return;
@@ -385,15 +405,24 @@ public class PassphraseDialogActivity extends FragmentActivity {
return;
}
- // cache the new passphrase
- Log.d(Constants.TAG, "Everything okay! Caching entered passphrase");
+ // cache the new passphrase as specified in CryptoInputParcel
+ Log.d(Constants.TAG, "Everything okay!");
- try {
- PassphraseCacheService.addCachedPassphrase(getActivity(),
- mSecretRing.getMasterKeyId(), mSubKeyId, passphrase,
- mSecretRing.getPrimaryUserIdWithFallback());
- } catch (PgpKeyNotFoundException e) {
- Log.e(Constants.TAG, "adding of a passphrase failed", e);
+ CryptoInputParcel cryptoInputParcel
+ = ((PassphraseDialogActivity) getActivity()).mCryptoInputParcel;
+
+ if (cryptoInputParcel.mCachePassphrase) {
+ Log.d(Constants.TAG, "Caching entered passphrase");
+
+ try {
+ PassphraseCacheService.addCachedPassphrase(getActivity(),
+ mSecretRing.getMasterKeyId(), mSubKeyId, passphrase,
+ mSecretRing.getPrimaryUserIdWithFallback());
+ } catch (PgpKeyNotFoundException e) {
+ Log.e(Constants.TAG, "adding of a passphrase failed", e);
+ }
+ } else {
+ Log.d(Constants.TAG, "Not caching entered passphrase!");
}
finishCaching(passphrase);
@@ -409,9 +438,12 @@ public class PassphraseDialogActivity extends FragmentActivity {
return;
}
- CryptoInputParcel inputParcel = new CryptoInputParcel(null, passphrase);
+ CryptoInputParcel inputParcel =
+ ((PassphraseDialogActivity) getActivity()).mCryptoInputParcel;
+ inputParcel.mPassphrase = passphrase;
if (mServiceIntent != null) {
- CryptoInputParcelCacheService.addCryptoInputParcel(getActivity(), mServiceIntent, inputParcel);
+ CryptoInputParcelCacheService.addCryptoInputParcel(getActivity(), mServiceIntent,
+ inputParcel);
getActivity().setResult(RESULT_OK, mServiceIntent);
} else {
// also return passphrase back to activity
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java
index 2e838535d..e55494145 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java
@@ -18,7 +18,7 @@
package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
-import android.app.AlertDialog;
+import android.support.v7.app.AlertDialog;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
@@ -43,7 +43,9 @@ import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
+import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -207,7 +209,7 @@ public class PassphraseWizardActivity extends FragmentActivity {
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
}
} catch (IOException | FormatException e) {
- e.printStackTrace();
+ Log.e(Constants.TAG, "Failed to write on NFC tag", e);
}
} else if (readNFC && AUTHENTICATION.equals(selectedAction)) {
@@ -232,7 +234,7 @@ public class PassphraseWizardActivity extends FragmentActivity {
}
}
} catch (IOException | FormatException e) {
- e.printStackTrace();
+ Log.e(Constants.TAG, "Failed to read NFC tag", e);
}
}
}
@@ -263,7 +265,7 @@ public class PassphraseWizardActivity extends FragmentActivity {
try {
password = readText(ndefRecord);
} catch (UnsupportedEncodingException e) {
- e.printStackTrace();
+ Log.e(Constants.TAG, "Failed to read password from tag", e);
}
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RetryUploadDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RetryUploadDialogActivity.java
new file mode 100644
index 000000000..2a00e8b70
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RetryUploadDialogActivity.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.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.ui;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.ContextThemeWrapper;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
+import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
+
+public class RetryUploadDialogActivity extends FragmentActivity {
+
+ public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input";
+
+ public static final String RESULT_CRYPTO_INPUT = "result_crypto_input";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ UploadRetryDialogFragment.newInstance().show(getSupportFragmentManager(),
+ "uploadRetryDialog");
+ }
+
+ public static class UploadRetryDialogFragment extends DialogFragment {
+ public static UploadRetryDialogFragment newInstance() {
+ return new UploadRetryDialogFragment();
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+
+ ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(getActivity());
+
+ CustomAlertDialogBuilder dialogBuilder = new CustomAlertDialogBuilder(theme);
+ dialogBuilder.setTitle(R.string.retry_up_dialog_title);
+ dialogBuilder.setMessage(R.string.retry_up_dialog_message);
+
+ dialogBuilder.setNegativeButton(R.string.retry_up_dialog_btn_cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ getActivity().setResult(RESULT_CANCELED);
+ getActivity().finish();
+ }
+ });
+
+ dialogBuilder.setPositiveButton(R.string.retry_up_dialog_btn_reupload,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent intent = new Intent();
+ intent.putExtra(RESULT_CRYPTO_INPUT, getActivity()
+ .getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT));
+ getActivity().setResult(RESULT_OK, intent);
+ getActivity().finish();
+ }
+ });
+
+ return dialogBuilder.show();
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java
index a1cb77546..534dbfd05 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java
@@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
+import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
@@ -81,7 +82,7 @@ public class SafeSlingerActivity extends BaseActivity
});
ImageView buttonIcon = (ImageView) findViewById(R.id.safe_slinger_button_image);
- buttonIcon.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
+ buttonIcon.setColorFilter(FormattingUtils.getColorFromAttr(this, R.attr.colorTertiaryText),
PorterDuff.Mode.SRC_IN);
View button = findViewById(R.id.safe_slinger_button);
@@ -144,7 +145,7 @@ public class SafeSlingerActivity extends BaseActivity
cache.writeCache(it.size(), it.iterator());
mOperationHelper =
- new CryptoOperationHelper(this, this, R.string.progress_importing);
+ new CryptoOperationHelper(1, this, this, R.string.progress_importing);
mKeyList = null;
mKeyserver = null;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
index a552e1c55..2fe868b8b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
@@ -18,14 +18,21 @@
package org.sufficientlysecure.keychain.ui;
+import android.annotation.TargetApi;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
+import android.preference.ListPreference;
import android.preference.Preference;
+import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
@@ -33,8 +40,12 @@ import android.widget.LinearLayout;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference;
+import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
import java.util.List;
@@ -42,15 +53,21 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
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 String ACTION_PREFS_PROXY = "org.sufficientlysecure.keychain.ui.PREFS_PROXY";
+ public static final String ACTION_PREFS_GUI = "org.sufficientlysecure.keychain.ui.PREFS_GUI";
public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005;
private PreferenceScreen mKeyServerPreference = null;
private static Preferences sPreferences;
+ private ThemeChanger mThemeChanger;
@Override
protected void onCreate(Bundle savedInstanceState) {
sPreferences = Preferences.getPreferences(this);
+
+ mThemeChanger = new ThemeChanger(this);
+ mThemeChanger.changeTheme();
super.onCreate(savedInstanceState);
setupToolbar();
@@ -95,6 +112,21 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
initializeUseNumKeypadForYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
+ } else if (action != null && action.equals(ACTION_PREFS_GUI)) {
+ addPreferencesFromResource(R.xml.gui_preferences);
+
+ initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (mThemeChanger.changeTheme()) {
+ Intent intent = getIntent();
+ finish();
+ startActivity(intent);
}
}
@@ -124,27 +156,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case REQUEST_CODE_KEYSERVER_PREF: {
- if (resultCode == RESULT_CANCELED || data == null) {
- return;
- }
- String servers[] = data
- .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS);
- sPreferences.setKeyServers(servers);
- mKeyServerPreference.setSummary(keyserverSummary(this));
- break;
- }
-
- default: {
- super.onActivityResult(requestCode, resultCode, data);
- break;
- }
- }
- }
-
- @Override
public void onBuildHeaders(List<Header> target) {
super.onBuildHeaders(target);
loadHeadersFromResource(R.xml.preference_headers, target);
@@ -190,12 +201,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_KEYSERVER_PREF: {
- if (resultCode == RESULT_CANCELED || data == null) {
- return;
- }
- String servers[] = data
- .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS);
- sPreferences.setKeyServers(servers);
+ // update preference, in case it changed
mKeyServerPreference.setSummary(keyserverSummary(getActivity()));
break;
}
@@ -234,9 +240,236 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
}
}
+ public static class ProxyPrefsFragment extends PreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ new Initializer(this).initialize();
+
+ }
+
+ public static class Initializer {
+ private CheckBoxPreference mUseTor;
+ private CheckBoxPreference mUseNormalProxy;
+ private EditTextPreference mProxyHost;
+ private EditTextPreference mProxyPort;
+ private ListPreference mProxyType;
+ private PreferenceActivity mActivity;
+ private PreferenceFragment mFragment;
+
+ public Initializer(PreferenceFragment fragment) {
+ mFragment = fragment;
+ }
+
+ public Initializer(PreferenceActivity activity) {
+ mActivity = activity;
+ }
+
+ public Preference automaticallyFindPreference(String key) {
+ if (mFragment != null) {
+ return mFragment.findPreference(key);
+ } else {
+ return mActivity.findPreference(key);
+ }
+ }
+
+ public void initialize() {
+ // makes android's preference framework write to our file instead of default
+ // This allows us to use the "persistent" attribute to simplify code
+ if (mFragment != null) {
+ Preferences.setPreferenceManagerFileAndMode(mFragment.getPreferenceManager());
+ // Load the preferences from an XML resource
+ mFragment.addPreferencesFromResource(R.xml.proxy_prefs);
+ } else {
+ Preferences.setPreferenceManagerFileAndMode(mActivity.getPreferenceManager());
+ // Load the preferences from an XML resource
+ mActivity.addPreferencesFromResource(R.xml.proxy_prefs);
+ }
+
+ mUseTor = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY);
+ mUseNormalProxy = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY);
+ mProxyHost = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_HOST);
+ mProxyPort = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_PORT);
+ mProxyType = (ListPreference) automaticallyFindPreference(Constants.Pref.PROXY_TYPE);
+ initializeUseTorPref();
+ initializeUseNormalProxyPref();
+ initializeEditTextPreferences();
+ initializeProxyTypePreference();
+
+ if (mUseTor.isChecked()) {
+ disableNormalProxyPrefs();
+ }
+ else if (mUseNormalProxy.isChecked()) {
+ disableUseTorPrefs();
+ } else {
+ disableNormalProxySettings();
+ }
+ }
+
+ private void initializeUseTorPref() {
+ mUseTor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Activity activity = mFragment != null ? mFragment.getActivity() : mActivity;
+ if ((Boolean) newValue) {
+ boolean installed = OrbotHelper.isOrbotInstalled(activity);
+ if (!installed) {
+ Log.d(Constants.TAG, "Prompting to install Tor");
+ OrbotHelper.getPreferenceInstallDialogFragment().show(activity.getFragmentManager(),
+ "installDialog");
+ // don't let the user check the box until he's installed orbot
+ return false;
+ } else {
+ disableNormalProxyPrefs();
+ // let the enable tor box be checked
+ return true;
+ }
+ } else {
+ // we're unchecking Tor, so enable other proxy
+ enableNormalProxyCheckbox();
+ return true;
+ }
+ }
+ });
+ }
+
+ private void initializeUseNormalProxyPref() {
+ mUseNormalProxy.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if ((Boolean) newValue) {
+ disableUseTorPrefs();
+ enableNormalProxySettings();
+ } else {
+ enableUseTorPrefs();
+ disableNormalProxySettings();
+ }
+ return true;
+ }
+ });
+ }
+
+ private void initializeEditTextPreferences() {
+ mProxyHost.setSummary(mProxyHost.getText());
+ mProxyPort.setSummary(mProxyPort.getText());
+
+ mProxyHost.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Activity activity = mFragment != null ? mFragment.getActivity() : mActivity;
+ if (TextUtils.isEmpty((String) newValue)) {
+ Notify.create(
+ activity,
+ R.string.pref_proxy_host_err_invalid,
+ Notify.Style.ERROR
+ ).show();
+ return false;
+ } else {
+ mProxyHost.setSummary((CharSequence) newValue);
+ return true;
+ }
+ }
+ });
+
+ mProxyPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Activity activity = mFragment != null ? mFragment.getActivity() : mActivity;
+ try {
+ int port = Integer.parseInt((String) newValue);
+ if (port < 0 || port > 65535) {
+ Notify.create(
+ activity,
+ R.string.pref_proxy_port_err_invalid,
+ Notify.Style.ERROR
+ ).show();
+ return false;
+ }
+ // no issues, save port
+ mProxyPort.setSummary("" + port);
+ return true;
+ } catch (NumberFormatException e) {
+ Notify.create(
+ activity,
+ R.string.pref_proxy_port_err_invalid,
+ Notify.Style.ERROR
+ ).show();
+ return false;
+ }
+ }
+ });
+ }
+
+ private void initializeProxyTypePreference() {
+ mProxyType.setSummary(mProxyType.getEntry());
+
+ mProxyType.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ CharSequence entry = mProxyType.getEntries()[mProxyType.findIndexOfValue((String) newValue)];
+ mProxyType.setSummary(entry);
+ return true;
+ }
+ });
+ }
+
+ private void disableNormalProxyPrefs() {
+ mUseNormalProxy.setChecked(false);
+ mUseNormalProxy.setEnabled(false);
+ disableNormalProxySettings();
+ }
+
+ private void enableNormalProxyCheckbox() {
+ mUseNormalProxy.setEnabled(true);
+ }
+
+ private void enableNormalProxySettings() {
+ mProxyHost.setEnabled(true);
+ mProxyPort.setEnabled(true);
+ mProxyType.setEnabled(true);
+ }
+
+ private void disableNormalProxySettings() {
+ mProxyHost.setEnabled(false);
+ mProxyPort.setEnabled(false);
+ mProxyType.setEnabled(false);
+ }
+
+ private void disableUseTorPrefs() {
+ mUseTor.setChecked(false);
+ mUseTor.setEnabled(false);
+ }
+
+ private void enableUseTorPrefs() {
+ mUseTor.setEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * This fragment shows gui preferences.
+ */
+ public static class GuiPrefsFragment extends PreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.gui_preferences);
+
+ initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
+ }
+ }
+
protected boolean isValidFragment(String fragmentName) {
return AdvancedPrefsFragment.class.getName().equals(fragmentName)
|| CloudSearchPrefsFragment.class.getName().equals(fragmentName)
+ || ProxyPrefsFragment.class.getName().equals(fragmentName)
+ || GuiPrefsFragment.class.getName().equals(fragmentName)
|| super.isValidFragment(fragmentName);
}
@@ -265,6 +498,19 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
});
}
+ private static void initializeTheme(final ListPreference mTheme) {
+ mTheme.setValue(sPreferences.getTheme());
+ mTheme.setSummary(mTheme.getEntry());
+ mTheme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mTheme.setValue((String) newValue);
+ mTheme.setSummary(mTheme.getEntry());
+ sPreferences.setTheme((String) newValue);
+ return false;
+ }
+ });
+ }
+
private static void initializeSearchKeyserver(final CheckBoxPreference mSearchKeyserver) {
Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs();
mSearchKeyserver.setChecked(prefs.searchKeyserver);
@@ -295,7 +541,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
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();
+ return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences
+ .getPreferredKeyserver();
}
private static void initializeUseDefaultYubiKeyPin(final CheckBoxPreference mUseDefaultYubiKeyPin) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java
index dc203756f..f61ada84f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java
@@ -17,89 +17,23 @@
package org.sufficientlysecure.keychain.ui;
-import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.os.Handler;
-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.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
-import org.sufficientlysecure.keychain.ui.dialog.AddKeyserverDialogFragment;
-import org.sufficientlysecure.keychain.ui.util.Notify;
-import org.sufficientlysecure.keychain.ui.widget.Editor;
-import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
-import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor;
-import java.util.Vector;
-
-public class SettingsKeyServerActivity extends BaseActivity implements OnClickListener,
- EditorListener {
+public class SettingsKeyServerActivity extends BaseActivity {
public static final String EXTRA_KEY_SERVERS = "key_servers";
- private LayoutInflater mInflater;
- private ViewGroup mEditors;
- private View mAdd;
- private View mRotate;
- private TextView mTitle;
- private TextView mSummary;
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Inflate a "Done"/"Cancel" custom action bar view
- setFullScreenDialogDoneClose(R.string.btn_save,
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- okClicked();
- }
- },
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- cancelClicked();
- }
- });
-
- mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- 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);
-
- mEditors = (ViewGroup) findViewById(R.id.editors);
- 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);
+ loadFragment(savedInstanceState, servers);
}
@Override
@@ -107,118 +41,22 @@ public class SettingsKeyServerActivity extends BaseActivity implements OnClickLi
setContentView(R.layout.key_server_preference);
}
- 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);
- view.setEditorListener(this);
- view.setValue(serv);
- mEditors.addView(view);
- }
+ private void loadFragment(Bundle savedInstanceState, String[] keyservers) {
+ // 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;
}
- }
-
- public void onDeleted(Editor editor, boolean wasNewItem) {
- // nothing to do
- }
- @Override
- public void onEdited() {
+ SettingsKeyserverFragment fragment = SettingsKeyserverFragment.newInstance(keyservers);
- }
-
- // button to add keyserver clicked
- public void onClick(View v) {
- Handler returnHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- Bundle data = message.getData();
- switch (message.what) {
- case AddKeyserverDialogFragment.MESSAGE_OKAY: {
- boolean verified = data.getBoolean(AddKeyserverDialogFragment.MESSAGE_VERIFIED);
- if (verified) {
- Notify.create(SettingsKeyServerActivity.this,
- R.string.add_keyserver_verified, Notify.Style.OK).show();
- } else {
- Notify.create(SettingsKeyServerActivity.this,
- R.string.add_keyserver_without_verification,
- Notify.Style.WARN).show();
- }
- String keyserver = data.getString(AddKeyserverDialogFragment.MESSAGE_KEYSERVER);
- addKeyserver(keyserver);
- break;
- }
- case AddKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: {
- AddKeyserverDialogFragment.FailureReason failureReason =
- (AddKeyserverDialogFragment.FailureReason) data.getSerializable(
- AddKeyserverDialogFragment.MESSAGE_FAILURE_REASON);
- switch (failureReason) {
- case CONNECTION_FAILED: {
- Notify.create(SettingsKeyServerActivity.this,
- R.string.add_keyserver_connection_failed,
- Notify.Style.ERROR).show();
- break;
- }
- case INVALID_URL: {
- Notify.create(SettingsKeyServerActivity.this,
- R.string.add_keyserver_invalid_url,
- Notify.Style.ERROR).show();
- break;
- }
- }
- break;
- }
- }
- }
- };
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(returnHandler);
- AddKeyserverDialogFragment dialogFragment = AddKeyserverDialogFragment
- .newInstance(messenger);
- dialogFragment.show(getSupportFragmentManager(), "addKeyserverDialog");
- }
-
- public void addKeyserver(String keyserverUrl) {
- KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor,
- mEditors, false);
- view.setEditorListener(this);
- view.setValue(keyserverUrl);
- mEditors.addView(view);
- }
-
- private void cancelClicked() {
- setResult(RESULT_CANCELED, null);
- finish();
- }
-
- private Vector<String> serverList() {
- Vector<String> servers = new Vector<>();
- 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<>();
- 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);
- }
- }
- String[] dummy = new String[0];
- data.putExtra(EXTRA_KEY_SERVERS, servers.toArray(dummy));
- setResult(RESULT_OK, data);
- finish();
+ // Add the fragment to the 'fragment_container' FrameLayout
+ // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.keyserver_settings_fragment_container, fragment)
+ .commitAllowingStateLoss();
+ // do it immediately!
+ getSupportFragmentManager().executePendingTransactions();
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
new file mode 100644
index 000000000..2ae64d90b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.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.ui;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.Fragment;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener;
+import org.sufficientlysecure.keychain.util.Preferences;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class SettingsKeyserverFragment extends Fragment implements RecyclerItemClickListener.OnItemClickListener {
+
+ private static final String ARG_KEYSERVER_ARRAY = "arg_keyserver_array";
+ private ItemTouchHelper mItemTouchHelper;
+
+ private ArrayList<String> mKeyservers;
+ private KeyserverListAdapter mAdapter;
+
+ public static SettingsKeyserverFragment newInstance(String[] keyservers) {
+ Bundle args = new Bundle();
+ args.putStringArray(ARG_KEYSERVER_ARRAY, keyservers);
+
+ SettingsKeyserverFragment fragment = new SettingsKeyserverFragment();
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
+ savedInstanceState) {
+
+ return inflater.inflate(R.layout.settings_keyserver_fragment, null);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ String keyservers[] = getArguments().getStringArray(ARG_KEYSERVER_ARRAY);
+ mKeyservers = new ArrayList<>(Arrays.asList(keyservers));
+
+ mAdapter = new KeyserverListAdapter(mKeyservers);
+
+ RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.keyserver_recycler_view);
+ // recyclerView.setHasFixedSize(true); // the size of the first item changes
+ recyclerView.setAdapter(mAdapter);
+ recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
+
+
+ ItemTouchHelper.Callback callback = new ItemTouchHelperDragCallback(mAdapter);
+ mItemTouchHelper = new ItemTouchHelper(callback);
+ mItemTouchHelper.attachToRecyclerView(recyclerView);
+
+ // for clicks
+ recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), this));
+
+ // can't use item decoration because it doesn't move with drag and drop
+ // recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null));
+
+ // We have a menu item to show in action bar.
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.keyserver_pref_menu, menu);
+
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+
+ case R.id.menu_add_keyserver:
+ startAddKeyserverDialog();
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void startAddKeyserverDialog() {
+ // keyserver and position have no meaning
+ startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.ADD, null, -1);
+ }
+
+ private void startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction action,
+ String keyserver, final int position) {
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ Bundle data = message.getData();
+ switch (message.what) {
+ case AddEditKeyserverDialogFragment.MESSAGE_OKAY: {
+ boolean deleted =
+ data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER_DELETED
+ , false);
+ if (deleted) {
+ Notify.create(getActivity(),
+ getActivity().getString(
+ R.string.keyserver_preference_deleted, mKeyservers.get(position)),
+ Notify.Style.OK)
+ .show();
+ deleteKeyserver(position);
+ return;
+ }
+ boolean verified =
+ data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_VERIFIED);
+ if (verified) {
+ Notify.create(getActivity(),
+ R.string.add_keyserver_verified, Notify.Style.OK).show();
+ } else {
+ Notify.create(getActivity(),
+ R.string.add_keyserver_without_verification,
+ Notify.Style.WARN).show();
+ }
+ String keyserver = data.getString(
+ AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER);
+
+ AddEditKeyserverDialogFragment.DialogAction dialogAction
+ = (AddEditKeyserverDialogFragment.DialogAction) data.getSerializable(
+ AddEditKeyserverDialogFragment.MESSAGE_DIALOG_ACTION);
+ switch (dialogAction) {
+ case ADD:
+ addKeyserver(keyserver);
+ break;
+ case EDIT:
+ editKeyserver(keyserver, position);
+ break;
+ }
+ break;
+ }
+ case AddEditKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: {
+ AddEditKeyserverDialogFragment.FailureReason failureReason =
+ (AddEditKeyserverDialogFragment.FailureReason) data.getSerializable(
+ AddEditKeyserverDialogFragment.MESSAGE_FAILURE_REASON);
+ switch (failureReason) {
+ case CONNECTION_FAILED: {
+ Notify.create(getActivity(),
+ R.string.add_keyserver_connection_failed,
+ Notify.Style.ERROR).show();
+ break;
+ }
+ case INVALID_URL: {
+ Notify.create(getActivity(),
+ R.string.add_keyserver_invalid_url,
+ Notify.Style.ERROR).show();
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+ AddEditKeyserverDialogFragment dialogFragment = AddEditKeyserverDialogFragment
+ .newInstance(messenger, action, keyserver, position);
+ dialogFragment.show(getFragmentManager(), "addKeyserverDialog");
+ }
+
+ private void addKeyserver(String keyserver) {
+ mKeyservers.add(keyserver);
+ mAdapter.notifyItemInserted(mKeyservers.size() - 1);
+ saveKeyserverList();
+ }
+
+ private void editKeyserver(String newKeyserver, int position) {
+ mKeyservers.set(position, newKeyserver);
+ mAdapter.notifyItemChanged(position);
+ saveKeyserverList();
+ }
+
+ private void deleteKeyserver(int position) {
+ if (mKeyservers.size() == 1) {
+ Notify.create(getActivity(), R.string.keyserver_preference_cannot_delete_last,
+ Notify.Style.ERROR).show();
+ return;
+ }
+ mKeyservers.remove(position);
+ // we use this
+ mAdapter.notifyItemRemoved(position);
+ if (position == 0 && mKeyservers.size() > 0) {
+ // if we deleted the first item, we need the adapter to redraw the new first item
+ mAdapter.notifyItemChanged(0);
+ }
+ saveKeyserverList();
+ }
+
+ private void saveKeyserverList() {
+ String servers[] = mKeyservers.toArray(new String[mKeyservers.size()]);
+ Preferences.getPreferences(getActivity()).setKeyServers(servers);
+ }
+
+ @Override
+ public void onItemClick(View view, int position) {
+ startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.EDIT,
+ mKeyservers.get(position), position);
+ }
+
+ public class KeyserverListAdapter extends RecyclerView.Adapter<KeyserverListAdapter.ViewHolder>
+ implements ItemTouchHelperAdapter {
+
+ // to update the ViewHolder associated with first item, for when an item is deleted
+ private ViewHolder mFirstItem;
+
+ private final List<String> mKeyservers;
+
+ public KeyserverListAdapter(List<String> keyservers) {
+ mKeyservers = keyservers;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.settings_keyserver_item, parent, false);
+ ViewHolder viewHolder = new ViewHolder(view);
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ if (position == 0) {
+ mFirstItem = holder;
+ }
+ holder.keyserverUrl.setText(mKeyservers.get(position));
+
+ // Start a drag whenever the handle view it touched
+ holder.dragHandleView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
+ mItemTouchHelper.startDrag(holder);
+ }
+ return false;
+ }
+ });
+
+ selectUnselectKeyserver(holder, position);
+ }
+
+ private void selectUnselectKeyserver(ViewHolder holder, int position) {
+
+ if (position == 0) {
+ holder.showAsSelectedKeyserver();
+ } else {
+ holder.showAsUnselectedKeyserver();
+ }
+ }
+
+ @Override
+ public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target,
+ int fromPosition, int toPosition) {
+ Collections.swap(mKeyservers, fromPosition, toPosition);
+ saveKeyserverList();
+ selectUnselectKeyserver((ViewHolder) target, fromPosition);
+ // we don't want source to change color while dragging, therefore we just set
+ // isSelectedKeyserver instead of selectUnselectKeyserver
+ ((ViewHolder) source).isSelectedKeyserver = toPosition == 0;
+
+ notifyItemMoved(fromPosition, toPosition);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mKeyservers.size();
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder implements
+ ItemTouchHelperViewHolder {
+
+ public final ViewGroup outerLayout;
+ public final TextView selectedServerLabel;
+ public final TextView keyserverUrl;
+ public final ImageView dragHandleView;
+
+ private boolean isSelectedKeyserver = false;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ outerLayout = (ViewGroup) itemView.findViewById(R.id.outer_layout);
+ selectedServerLabel = (TextView) itemView.findViewById(
+ R.id.selected_keyserver_title);
+ keyserverUrl = (TextView) itemView.findViewById(R.id.keyserver_tv);
+ dragHandleView = (ImageView) itemView.findViewById(R.id.drag_handle);
+
+ itemView.setClickable(true);
+ }
+
+ public void showAsSelectedKeyserver() {
+ isSelectedKeyserver = true;
+ selectedServerLabel.setVisibility(View.VISIBLE);
+ outerLayout.setBackgroundColor(getResources().getColor(R.color.android_green_dark));
+ }
+
+ public void showAsUnselectedKeyserver() {
+ isSelectedKeyserver = false;
+ selectedServerLabel.setVisibility(View.GONE);
+ outerLayout.setBackgroundColor(Color.WHITE);
+ }
+
+ @Override
+ public void onItemSelected() {
+ selectedServerLabel.setVisibility(View.GONE);
+ itemView.setBackgroundColor(Color.LTGRAY);
+ }
+
+ @Override
+ public void onItemClear() {
+ if (isSelectedKeyserver) {
+ showAsSelectedKeyserver();
+ } else {
+ showAsUnselectedKeyserver();
+ }
+ }
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
index 8b49f3b96..0415128a2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
@@ -26,7 +26,6 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
-import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -108,7 +107,7 @@ public class UploadKeyActivity extends BaseActivity
String server = (String) mKeyServerSpinner.getSelectedItem();
mKeyserver = server;
- mUploadOpHelper = new CryptoOperationHelper(this, this, R.string.progress_uploading);
+ mUploadOpHelper = new CryptoOperationHelper(1, this, this, R.string.progress_uploading);
mUploadOpHelper.cryptoOperation();
}
@@ -132,8 +131,7 @@ public class UploadKeyActivity extends BaseActivity
@Override
public void onCryptoOperationSuccess(ExportResult result) {
- Toast.makeText(UploadKeyActivity.this, R.string.msg_crt_upload_success,
- Toast.LENGTH_SHORT).show();
+ result.createNotify(this).show();
}
@Override
@@ -143,7 +141,7 @@ public class UploadKeyActivity extends BaseActivity
@Override
public void onCryptoOperationError(ExportResult result) {
- // TODO: Implement proper log for key upload then show error
+ result.createNotify(this).show();
}
@Override
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
index d3849c892..1d0e085da 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
@@ -31,8 +31,6 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Message;
-import android.os.Messenger;
import android.provider.ContactsContract;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager;
@@ -52,11 +50,12 @@ import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
+
import com.getbase.floatingactionbutton.FloatingActionButton;
+
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
-import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
@@ -66,13 +65,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
-import org.sufficientlysecure.keychain.service.KeychainService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
-import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
-import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
@@ -88,7 +82,6 @@ import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.HashMap;
public class ViewKeyActivity extends BaseNfcActivity implements
LoaderManager.LoaderCallbacks<Cursor>,
@@ -99,11 +92,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements
public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints";
static final int REQUEST_QR_FINGERPRINT = 1;
- static final int REQUEST_DELETE = 2;
- static final int REQUEST_EXPORT = 3;
+ static final int REQUEST_BACKUP = 2;
+ static final int REQUEST_CERTIFY = 3;
+ static final int REQUEST_DELETE = 4;
+
public static final String EXTRA_DISPLAY_RESULT = "display_result";
- ExportHelper mExportHelper;
ProviderHelper mProviderHelper;
protected Uri mDataUri;
@@ -148,13 +142,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements
private String mFingerprint;
private long mMasterKeyId;
+ private byte[] mNfcFingerprints;
+ private String mNfcUserId;
+ private byte[] mNfcAid;
+
@SuppressLint("InflateParams")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mExportHelper = new ExportHelper(this);
mProviderHelper = new ProviderHelper(this);
+ mOperationHelper = new CryptoOperationHelper<>(1, this, this, null);
setTitle(null);
@@ -325,31 +323,11 @@ public class ViewKeyActivity extends BaseNfcActivity implements
return true;
}
case R.id.menu_key_view_export_file: {
- try {
- if (PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId, mMasterKeyId) != null) {
- exportToFile(mDataUri, mExportHelper, mProviderHelper);
- return true;
- }
-
- startPassphraseActivity(REQUEST_EXPORT);
- } catch (PassphraseCacheService.KeyNotFoundException e) {
- // This happens when the master key is stripped
- exportToFile(mDataUri, mExportHelper, mProviderHelper);
- }
+ startPassphraseActivity(REQUEST_BACKUP);
return true;
}
case R.id.menu_key_view_delete: {
- try {
- if (PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId, mMasterKeyId) != null) {
- deleteKey();
- return true;
- }
-
- startPassphraseActivity(REQUEST_DELETE);
- } catch (PassphraseCacheService.KeyNotFoundException e) {
- // This happens when the master key is stripped
- deleteKey();
- }
+ deleteKey();
return true;
}
case R.id.menu_key_view_advanced: {
@@ -382,6 +360,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem editKey = menu.findItem(R.id.menu_key_view_edit);
editKey.setVisible(mIsSecret);
+ MenuItem exportKey = menu.findItem(R.id.menu_key_view_export_file);
+ exportKey.setVisible(mIsSecret);
MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint);
certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked);
@@ -399,37 +379,14 @@ public class ViewKeyActivity extends BaseNfcActivity implements
Intent intent = new Intent(this, CertifyFingerprintActivity.class);
intent.setData(dataUri);
- startCertifyIntent(intent);
+ startActivityForResult(intent, REQUEST_CERTIFY);
}
private void certifyImmediate() {
Intent intent = new Intent(this, CertifyKeyActivity.class);
intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId});
- startCertifyIntent(intent);
- }
-
- private void startCertifyIntent(Intent intent) {
- // Message is received after signing is done in KeychainService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) {
- @Override
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- Bundle data = message.getData();
- CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
-
- result.createNotify(ViewKeyActivity.this).show();
- }
- }
- };
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
-
- startActivityForResult(intent, 0);
+ startActivityForResult(intent, REQUEST_CERTIFY);
}
private void showQrCodeDialog() {
@@ -454,99 +411,95 @@ public class ViewKeyActivity extends BaseNfcActivity implements
startActivityForResult(intent, requestCode);
}
- private void exportToFile(Uri dataUri, ExportHelper exportHelper, ProviderHelper providerHelper) {
- try {
- Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri);
-
- HashMap<String, Object> data = providerHelper.getGenericData(
- baseUri,
- new String[] {KeychainContract.Keys.MASTER_KEY_ID, KeychainContract.KeyRings.HAS_SECRET},
- new int[] {ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_INTEGER});
-
- exportHelper.showExportKeysDialog(
- new long[] {(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)},
- Constants.Path.APP_DIR_FILE, ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) != 0)
- );
- } catch (ProviderHelper.NotFoundException e) {
- Notify.create(this, R.string.error_key_not_found, Notify.Style.ERROR).show();
- Log.e(Constants.TAG, "Key not found", e);
- }
+ private void backupToFile() {
+ new ExportHelper(this).showExportKeysDialog(
+ mMasterKeyId, Constants.Path.APP_DIR_FILE, true);
}
private void deleteKey() {
- // Message is received after key is deleted
- Handler returnHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- setResult(RESULT_CANCELED);
- finish();
- }
- }
- };
+ Intent deleteIntent = new Intent(this, DeleteKeyDialogActivity.class);
+
+ deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS,
+ new long[]{mMasterKeyId});
+ deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, mIsSecret);
+ if (mIsSecret) {
+ // for upload in case key is secret
+ deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER,
+ Preferences.getPreferences(this).getPreferredKeyserver());
+ }
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(returnHandler);
- DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
- new long[] {mMasterKeyId});
- deleteKeyDialog.show(getSupportFragmentManager(), "deleteKeyDialog");
+ startActivityForResult(deleteIntent, REQUEST_DELETE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (mOperationHelper != null) {
- mOperationHelper.handleActivityResult(requestCode, resultCode, data);
+ if (mOperationHelper.handleActivityResult(requestCode, resultCode, data)) {
+ return;
+ }
+
+ if (resultCode != Activity.RESULT_OK) {
+ return;
}
- if (requestCode == REQUEST_QR_FINGERPRINT && resultCode == Activity.RESULT_OK) {
+ switch (requestCode) {
+ case REQUEST_QR_FINGERPRINT: {
- // If there is an EXTRA_RESULT, that's an error. Just show it.
- if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
- OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
- result.createNotify(this).show();
+ // If there is an EXTRA_RESULT, that's an error. Just show it.
+ if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
+ OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
+ result.createNotify(this).show();
+ return;
+ }
+
+ String fp = data.getStringExtra(ImportKeysProxyActivity.EXTRA_FINGERPRINT);
+ if (fp == null) {
+ Notify.create(this, R.string.error_scan_fp, Notify.LENGTH_LONG, Style.ERROR).show();
+ return;
+ }
+ if (mFingerprint.equalsIgnoreCase(fp)) {
+ certifyImmediate();
+ } else {
+ Notify.create(this, R.string.error_scan_match, Notify.LENGTH_LONG, Style.ERROR).show();
+ }
return;
}
- String fp = data.getStringExtra(ImportKeysProxyActivity.EXTRA_FINGERPRINT);
- if (fp == null) {
- Notify.create(this, "Error scanning fingerprint!",
- Notify.LENGTH_LONG, Notify.Style.ERROR).show();
+ case REQUEST_BACKUP: {
+ backupToFile();
return;
}
- if (mFingerprint.equalsIgnoreCase(fp)) {
- certifyImmediate();
- } else {
- Notify.create(this, "Fingerprints did not match!",
- Notify.LENGTH_LONG, Notify.Style.ERROR).show();
- }
- return;
- }
+ case REQUEST_CERTIFY: {
+ if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
+ OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
+ result.createNotify(this).show();
+ }
+ return;
+ }
- if (requestCode == REQUEST_DELETE && resultCode == Activity.RESULT_OK) {
- deleteKey();
+ case REQUEST_DELETE: {
+ setResult(RESULT_OK, data);
+ finish();
+ return;
+ }
}
- if (requestCode == REQUEST_EXPORT && resultCode == Activity.RESULT_OK) {
- exportToFile(mDataUri, mExportHelper, mProviderHelper);
- }
+ super.onActivityResult(requestCode, resultCode, data);
- if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
- OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
- result.createNotify(this).show();
- } else {
- super.onActivityResult(requestCode, resultCode, data);
- }
}
@Override
- protected void onNfcPerform() throws IOException {
+ protected void doNfcInBackground() throws IOException {
+
+ mNfcFingerprints = nfcGetFingerprints();
+ mNfcUserId = nfcGetUserId();
+ mNfcAid = nfcGetAid();
+ }
- final byte[] nfcFingerprints = nfcGetFingerprints();
- final String nfcUserId = nfcGetUserId();
- final byte[] nfcAid = nfcGetAid();
+ @Override
+ protected void onNfcPostExecute() throws IOException {
- long yubiKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints);
+ long yubiKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
try {
@@ -557,7 +510,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
// if the master key of that key matches this one, just show the yubikey dialog
if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprint)) {
- showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid);
+ showYubiKeyFragment(mNfcFingerprints, mNfcUserId, mNfcAid);
return;
}
@@ -570,9 +523,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements
Intent intent = new Intent(
ViewKeyActivity.this, ViewKeyActivity.class);
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints);
startActivity(intent);
finish();
}
@@ -586,15 +539,14 @@ public class ViewKeyActivity extends BaseNfcActivity implements
public void onAction() {
Intent intent = new Intent(
ViewKeyActivity.this, CreateKeyActivity.class);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints);
startActivity(intent);
finish();
}
}, R.string.snack_yubikey_import).show();
}
-
}
public void showYubiKeyFragment(
@@ -629,7 +581,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
long keyId = new ProviderHelper(this)
.getCachedPublicKeyRing(dataUri)
.extractOrGetMasterKeyId();
- long[] encryptionKeyIds = new long[] {keyId};
+ long[] encryptionKeyIds = new long[]{keyId};
Intent intent;
if (text) {
intent = new Intent(this, EncryptTextActivity.class);
@@ -647,38 +599,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements
}
}
- private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper)
- throws ProviderHelper.NotFoundException {
-
- mIsRefreshing = true;
- mRefreshItem.setEnabled(false);
- mRefreshItem.setActionView(mRefresh);
- mRefresh.startAnimation(mRotate);
-
- byte[] blob = (byte[]) providerHelper.getGenericData(
- KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
- KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
- String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
-
- ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
- ArrayList<ParcelableKeyRing> entries = new ArrayList<>();
- entries.add(keyEntry);
- mKeyList = entries;
-
- // search config
- {
- Preferences prefs = Preferences.getPreferences(this);
- Preferences.CloudSearchPrefs cloudPrefs =
- new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
- mKeyserver = cloudPrefs.keyserver;
- }
-
- mOperationHelper = new CryptoOperationHelper<>(
- this, this, R.string.progress_importing);
-
- mOperationHelper.cryptoOperation();
- }
-
private void editKey(Uri dataUri) {
Intent editIntent = new Intent(this, EditKeyActivity.class);
editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
@@ -735,7 +655,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
// These are the rows that we will retrieve.
- static final String[] PROJECTION = new String[] {
+ static final String[] PROJECTION = new String[]{
KeychainContract.KeyRings._ID,
KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.KeyRings.USER_ID,
@@ -771,6 +691,25 @@ public class ViewKeyActivity extends BaseNfcActivity implements
int mPreviousColor = 0;
+ /**
+ * Calculate a reasonable color for the status bar based on the given toolbar color.
+ * Style guides want the toolbar color to be a "700" on the Android scale and the status
+ * bar should be the same color at "500", this is roughly 17 / 20th of the value in each
+ * channel.
+ * http://www.google.com/design/spec/style/color.html#color-color-palette
+ */
+ static public int getStatusBarBackgroundColor(int color) {
+ int r = (color >> 16) & 0xff;
+ int g = (color >> 8) & 0xff;
+ int b = color & 0xff;
+
+ r = r * 17 / 20;
+ g = g * 17 / 20;
+ b = b * 17 / 20;
+
+ return (0xff << 24) | (r << 16) | (g << 8) | b;
+ }
+
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
/* TODO better error handling? May cause problems when a key is deleted,
@@ -823,7 +762,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements
AsyncTask<Long, Void, Bitmap> photoTask =
new AsyncTask<Long, Void, Bitmap>() {
protected Bitmap doInBackground(Long... mMasterKeyId) {
- return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), mMasterKeyId[0], true);
+ return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(),
+ mMasterKeyId[0], true);
}
protected void onPostExecute(Bitmap photo) {
@@ -839,7 +779,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
State.REVOKED, R.color.icons, true);
- color = getResources().getColor(R.color.android_red_light);
+ color = getResources().getColor(R.color.key_flag_red);
mActionEncryptFile.setVisibility(View.GONE);
mActionEncryptText.setVisibility(View.GONE);
@@ -855,7 +795,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
State.EXPIRED, R.color.icons, true);
- color = getResources().getColor(R.color.android_red_light);
+ color = getResources().getColor(R.color.key_flag_red);
mActionEncryptFile.setVisibility(View.GONE);
mActionEncryptText.setVisibility(View.GONE);
@@ -865,7 +805,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
} else if (mIsSecret) {
mStatusText.setText(R.string.view_key_my_key);
mStatusImage.setVisibility(View.GONE);
- color = getResources().getColor(R.color.primary);
+ color = getResources().getColor(R.color.key_flag_green);
// reload qr code only if the fingerprint changed
if (!mFingerprint.equals(mQrCodeLoaded)) {
loadQrCode(mFingerprint);
@@ -903,6 +843,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mActionNfc.setVisibility(View.GONE);
}
mFab.setVisibility(View.VISIBLE);
+ // noinspection deprecation (no getDrawable with theme at current minApi level 15!)
mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
} else {
mActionEncryptFile.setVisibility(View.VISIBLE);
@@ -915,7 +856,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
State.VERIFIED, R.color.icons, true);
- color = getResources().getColor(R.color.primary);
+ color = getResources().getColor(R.color.key_flag_green);
photoTask.execute(mMasterKeyId);
mFab.setVisibility(View.GONE);
@@ -924,20 +865,21 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
State.UNVERIFIED, R.color.icons, true);
- color = getResources().getColor(R.color.android_orange_light);
+ color = getResources().getColor(R.color.key_flag_orange);
mFab.setVisibility(View.VISIBLE);
}
}
if (mPreviousColor == 0 || mPreviousColor == color) {
- mStatusBar.setBackgroundColor(color);
+ mStatusBar.setBackgroundColor(getStatusBarBackgroundColor(color));
mBigToolbar.setBackgroundColor(color);
mPreviousColor = color;
} else {
ObjectAnimator colorFade1 =
ObjectAnimator.ofObject(mStatusBar, "backgroundColor",
- new ArgbEvaluator(), mPreviousColor, color);
+ new ArgbEvaluator(), mPreviousColor,
+ getStatusBarBackgroundColor(color));
ObjectAnimator colorFade2 =
ObjectAnimator.ofObject(mBigToolbar, "backgroundColor",
new ArgbEvaluator(), mPreviousColor, color);
@@ -965,6 +907,36 @@ public class ViewKeyActivity extends BaseNfcActivity implements
// CryptoOperationHelper.Callback functions
+
+ private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper)
+ throws ProviderHelper.NotFoundException {
+
+ mIsRefreshing = true;
+ mRefreshItem.setEnabled(false);
+ mRefreshItem.setActionView(mRefresh);
+ mRefresh.startAnimation(mRotate);
+
+ byte[] blob = (byte[]) providerHelper.getGenericData(
+ KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
+ KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
+ String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
+
+ ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
+ ArrayList<ParcelableKeyRing> entries = new ArrayList<>();
+ entries.add(keyEntry);
+ mKeyList = entries;
+
+ // search config
+ {
+ Preferences prefs = Preferences.getPreferences(this);
+ Preferences.CloudSearchPrefs cloudPrefs =
+ new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
+ mKeyserver = cloudPrefs.keyserver;
+ }
+
+ mOperationHelper.cryptoOperation();
+ }
+
@Override
public ImportKeyringParcel createOperationInput() {
return new ImportKeyringParcel(mKeyList, mKeyserver);
@@ -989,6 +961,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements
@Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
- return false;
+ return true;
}
-} \ No newline at end of file
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java
index 6669f2654..673092e61 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java
@@ -211,18 +211,18 @@ public class ViewKeyAdvActivity extends BaseActivity implements
// Note: order is important
int color;
if (isRevoked || isExpired) {
- color = getResources().getColor(R.color.android_red_light);
+ color = getResources().getColor(R.color.key_flag_red);
} else if (isSecret) {
- color = getResources().getColor(R.color.primary);
+ color = getResources().getColor(R.color.android_green_light);
} else {
if (isVerified) {
- color = getResources().getColor(R.color.primary);
+ color = getResources().getColor(R.color.android_green_light);
} else {
- color = getResources().getColor(R.color.android_orange_light);
+ color = getResources().getColor(R.color.key_flag_orange);
}
}
mToolbar.setBackgroundColor(color);
- mStatusBar.setBackgroundColor(color);
+ mStatusBar.setBackgroundColor(ViewKeyActivity.getStatusBarBackgroundColor(color));
mSlidingTabLayout.setBackgroundColor(color);
break;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java
index b44e6dc78..4a46896bc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java
@@ -25,6 +25,9 @@ import java.io.OutputStreamWriter;
import android.app.Activity;
import android.app.ActivityOptions;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
@@ -49,16 +52,18 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
+import org.sufficientlysecure.keychain.util.ExportHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.NfcHelper;
@@ -79,6 +84,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
private Uri mDataUri;
private byte[] mFingerprint;
+ private long mMasterKeyId;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
@@ -101,11 +107,12 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
View vFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share);
View vFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard);
View vKeyShareButton = view.findViewById(R.id.view_key_action_key_share);
+ View vKeySafeButton = view.findViewById(R.id.view_key_action_key_export);
View vKeyNfcButton = view.findViewById(R.id.view_key_action_key_nfc);
View vKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
ImageButton vKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger);
View vKeyUploadButton = view.findViewById(R.id.view_key_action_upload);
- vKeySafeSlingerButton.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
+ vKeySafeSlingerButton.setColorFilter(FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorTertiaryText),
PorterDuff.Mode.SRC_IN);
vFingerprintShareButton.setOnClickListener(new View.OnClickListener() {
@@ -126,6 +133,12 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
share(false, false);
}
});
+ vKeySafeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ exportToFile();
+ }
+ });
vKeyClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -161,6 +174,11 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
return root;
}
+ private void exportToFile() {
+ new ExportHelper(getActivity()).showExportKeysDialog(
+ mMasterKeyId, Constants.Path.APP_DIR_FILE, false);
+ }
+
private void startSafeSlinger(Uri dataUri) {
long keyId = 0;
try {
@@ -197,7 +215,15 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
if (toClipboard) {
- ClipboardReflection.copyToClipboard(activity, content);
+ ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
+ if (clipMan == null) {
+ Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR);
+ return;
+ }
+
+ ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, content);
+ clipMan.setPrimaryClip(clip);
+
Notify.create(activity, fingerprintOnly ? R.string.fingerprint_copied_to_clipboard
: R.string.key_copied_to_clipboard, Notify.Style.OK).show();
return;
@@ -344,6 +370,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
/** Load QR Code asynchronously and with a fade in animation */
private void setFingerprint(byte[] fingerprintBlob) {
mFingerprint = fingerprintBlob;
+ mMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(fingerprintBlob);
final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
mFingerprintView.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java
index 092ab40d6..b118c3ad0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java
@@ -51,6 +51,9 @@ import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ParcelableProxy;
+import org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
import java.util.ArrayList;
import java.util.Hashtable;
@@ -197,8 +200,22 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
mStartSearch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- mStartSearch.setEnabled(false);
- new DescribeKey().execute(fingerprint);
+ final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity())
+ .getProxyPrefs();
+
+ Runnable ignoreTor = new Runnable() {
+ @Override
+ public void run() {
+ mStartSearch.setEnabled(false);
+ new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
+ }
+ };
+
+ if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs,
+ getActivity())) {
+ mStartSearch.setEnabled(false);
+ new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
+ }
}
});
}
@@ -229,6 +246,11 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
// look for evidence from keybase in the background, make tabular version of result
//
private class DescribeKey extends AsyncTask<String, Void, ResultPage> {
+ ParcelableProxy mParcelableProxy;
+
+ public DescribeKey(ParcelableProxy parcelableProxy) {
+ mParcelableProxy = parcelableProxy;
+ }
@Override
protected ResultPage doInBackground(String... args) {
@@ -237,7 +259,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
final ArrayList<CharSequence> proofList = new ArrayList<CharSequence>();
final Hashtable<Integer, ArrayList<Proof>> proofs = new Hashtable<Integer, ArrayList<Proof>>();
try {
- User keybaseUser = User.findByFingerprint(fingerprint);
+ User keybaseUser = User.findByFingerprint(fingerprint, mParcelableProxy.getProxy());
for (Proof proof : keybaseUser.getProofs()) {
Integer proofType = proof.getType();
appendIfOK(proofs, proofType, proof);
@@ -376,7 +398,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
mProofVerifyDetail.setVisibility(View.GONE);
- mKeybaseOpHelper = new CryptoOperationHelper<>(this, this,
+ mKeybaseOpHelper = new CryptoOperationHelper<>(1, this, this,
R.string.progress_verifying_signature);
mKeybaseOpHelper.cryptoOperation();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java
index f8c3b59ea..f980f297b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.ui;
+
import java.nio.ByteBuffer;
import java.util.Arrays;
@@ -39,12 +40,12 @@ import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.service.PromoteKeyringParcel;
-import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
+import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
public class ViewKeyYubiKeyFragment
- extends CryptoOperationFragment<PromoteKeyringParcel, PromoteKeyResult>
+ extends QueueingCryptoOperationFragment<PromoteKeyringParcel, PromoteKeyResult>
implements LoaderCallbacks<Cursor> {
public static final String ARG_MASTER_KEY_ID = "master_key_id";
@@ -75,6 +76,10 @@ public class ViewKeyYubiKeyFragment
return frag;
}
+ public ViewKeyYubiKeyFragment() {
+ super(null);
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -210,7 +215,8 @@ public class ViewKeyYubiKeyFragment
}
@Override
- protected void onCryptoOperationResult(PromoteKeyResult result) {
+ public void onQueuedOperationSuccess(PromoteKeyResult result) {
result.createNotify(getActivity()).show();
}
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java
index 93c6593ab..0be7e8f76 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java
@@ -177,9 +177,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
}
if (entry.isRevoked()) {
- KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.REVOKED, R.color.bg_gray);
+ KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.REVOKED, R.color.key_flag_gray);
} else if (entry.isExpired()) {
- KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.EXPIRED, R.color.bg_gray);
+ KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.EXPIRED, R.color.key_flag_gray);
}
if (entry.isRevoked() || entry.isExpired()) {
@@ -188,9 +188,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
// no more space for algorithm display
holder.algorithm.setVisibility(View.GONE);
- holder.mainUserId.setTextColor(getContext().getResources().getColor(R.color.bg_gray));
- holder.mainUserIdRest.setTextColor(getContext().getResources().getColor(R.color.bg_gray));
- holder.keyId.setTextColor(getContext().getResources().getColor(R.color.bg_gray));
+ holder.mainUserId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
+ holder.mainUserIdRest.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
+ holder.keyId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
} else {
holder.status.setVisibility(View.GONE);
holder.algorithm.setVisibility(View.VISIBLE);
@@ -198,11 +198,11 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
if (entry.isSecretKey()) {
holder.mainUserId.setTextColor(Color.RED);
} else {
- holder.mainUserId.setTextColor(Color.BLACK);
+ holder.mainUserId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
}
- holder.mainUserIdRest.setTextColor(Color.BLACK);
- holder.keyId.setTextColor(Color.BLACK);
+ holder.mainUserIdRest.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
+ holder.keyId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
}
if (entry.getUserIds().size() == 1) {
@@ -242,9 +242,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
uidView.setPadding(0, 0, FormattingUtils.dpToPx(getContext(), 8), 0);
if (entry.isRevoked() || entry.isExpired()) {
- uidView.setTextColor(getContext().getResources().getColor(R.color.bg_gray));
+ uidView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
} else {
- uidView.setTextColor(getContext().getResources().getColor(R.color.black));
+ uidView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText));
}
holder.userIdsList.addView(uidView);
@@ -258,9 +258,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
emailView.setText(highlighter.highlight(email));
if (entry.isRevoked() || entry.isExpired()) {
- emailView.setTextColor(getContext().getResources().getColor(R.color.bg_gray));
+ emailView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
} else {
- emailView.setTextColor(getContext().getResources().getColor(R.color.black));
+ emailView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText));
}
holder.userIdsList.addView(emailView);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java
index af919f3b6..e77c92923 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.ui.adapter;
import android.content.Context;
+import android.support.annotation.Nullable;
import android.support.v4.content.AsyncTaskLoader;
import org.sufficientlysecure.keychain.Constants;
@@ -26,8 +27,12 @@ import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.Keyserver;
import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
import java.util.ArrayList;
@@ -38,15 +43,27 @@ public class ImportKeysListCloudLoader
Preferences.CloudSearchPrefs mCloudPrefs;
String mServerQuery;
+ private ParcelableProxy mParcelableProxy;
private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<>();
private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
- public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) {
+ /**
+ * Searches a keyserver as specified in cloudPrefs, using an explicit proxy if passed
+ *
+ * @param serverQuery string to search on servers for. If is a fingerprint,
+ * will enforce fingerprint check
+ * @param cloudPrefs contains keyserver to search on, whether to search on the keyserver,
+ * and whether to search keybase.io
+ * @param parcelableProxy explicit proxy to use. If null, will retrieve from preferences
+ */
+ public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs,
+ @Nullable ParcelableProxy parcelableProxy) {
super(context);
mContext = context;
mServerQuery = serverQuery;
mCloudPrefs = cloudPrefs;
+ mParcelableProxy = parcelableProxy;
}
@Override
@@ -95,9 +112,32 @@ public class ImportKeysListCloudLoader
* Query keyserver
*/
private void queryServer(boolean enforceFingerprint) {
+ ParcelableProxy parcelableProxy;
+
+ if (mParcelableProxy == null) {
+ // no explicit proxy specified, fetch from preferences
+ if (OrbotHelper.isOrbotInRequiredState(mContext)) {
+ parcelableProxy = Preferences.getPreferences(mContext).getProxyPrefs()
+ .parcelableProxy;
+ } else {
+ // user needs to enable/install orbot
+ mEntryList.clear();
+ GetKeyResult pendingResult = new GetKeyResult(null,
+ RequiredInputParcel.createOrbotRequiredOperation(),
+ new CryptoInputParcel());
+ mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, pendingResult);
+ return;
+ }
+ } else {
+ parcelableProxy = mParcelableProxy;
+ }
+
try {
- ArrayList<ImportKeysListEntry> searchResult
- = CloudSearch.search(mServerQuery, mCloudPrefs);
+ ArrayList<ImportKeysListEntry> searchResult = CloudSearch.search(
+ mServerQuery,
+ mCloudPrefs,
+ parcelableProxy.getProxy()
+ );
mEntryList.clear();
// add result to data
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java
index e545b007b..aba1eb0d8 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java
@@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui.adapter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Date;
import java.util.List;
@@ -43,6 +42,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.ui.util.Highlighter;
+import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
@@ -50,6 +50,7 @@ public class KeyAdapter extends CursorAdapter {
protected String mQuery;
protected LayoutInflater mInflater;
+ protected Context mContext;
// These are the rows that we will retrieve.
public static final String[] PROJECTION = new String[]{
@@ -78,6 +79,7 @@ public class KeyAdapter extends CursorAdapter {
public KeyAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
+ mContext = context;
mInflater = LayoutInflater.from(context);
}
@@ -107,7 +109,7 @@ public class KeyAdapter extends CursorAdapter {
mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation);
}
- public void setData(Context context, KeyItem item, Highlighter highlighter) {
+ public void setData(Context context, KeyItem item, Highlighter highlighter, boolean enabled) {
mDisplayedItem = item;
@@ -126,36 +128,39 @@ public class KeyAdapter extends CursorAdapter {
}
}
+ // sort of a hack: if this item isn't enabled, we make it clickable
+ // to intercept its click events
+ mView.setClickable(!enabled);
+
{ // set edit button and status, specific by key type
mMasterKeyId = item.mKeyId;
+ int textColor;
+
// Note: order is important!
if (item.mIsRevoked) {
KeyFormattingUtils
- .setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray);
+ .setStatusImage(context, mStatus, null, State.REVOKED, R.color.key_flag_gray);
mStatus.setVisibility(View.VISIBLE);
mSlinger.setVisibility(View.GONE);
- mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
- mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
+ textColor = context.getResources().getColor(R.color.key_flag_gray);
} else if (item.mIsExpired) {
- KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray);
+ KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.key_flag_gray);
mStatus.setVisibility(View.VISIBLE);
mSlinger.setVisibility(View.GONE);
- mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
- mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
+ textColor = context.getResources().getColor(R.color.key_flag_gray);
} else if (item.mIsSecret) {
mStatus.setVisibility(View.GONE);
if (mSlingerButton.hasOnClickListeners()) {
mSlingerButton.setColorFilter(
- context.getResources().getColor(R.color.tertiary_text_light),
+ FormattingUtils.getColorFromAttr(context, R.attr.colorTertiaryText),
PorterDuff.Mode.SRC_IN);
mSlinger.setVisibility(View.VISIBLE);
} else {
mSlinger.setVisibility(View.GONE);
}
- mMainUserId.setTextColor(context.getResources().getColor(R.color.black));
- mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
+ textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText);
} else {
// this is a public key - show if it's verified
if (item.mIsVerified) {
@@ -166,10 +171,16 @@ public class KeyAdapter extends CursorAdapter {
mStatus.setVisibility(View.VISIBLE);
}
mSlinger.setVisibility(View.GONE);
- mMainUserId.setTextColor(context.getResources().getColor(R.color.black));
- mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
+ textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText);
+ }
+
+ if (!enabled) {
+ textColor = context.getResources().getColor(R.color.key_flag_gray);
}
+ mMainUserId.setTextColor(textColor);
+ mMainUserIdRest.setTextColor(textColor);
+
if (item.mHasDuplicate) {
String dateTime = DateUtils.formatDateTime(context,
item.mCreation.getTime(),
@@ -179,6 +190,7 @@ public class KeyAdapter extends CursorAdapter {
mCreationDate.setText(context.getString(R.string.label_key_created,
dateTime));
+ mCreationDate.setTextColor(textColor);
mCreationDate.setVisibility(View.VISIBLE);
} else {
mCreationDate.setVisibility(View.GONE);
@@ -205,9 +217,11 @@ public class KeyAdapter extends CursorAdapter {
@Override
public void bindView(View view, Context context, Cursor cursor) {
Highlighter highlighter = new Highlighter(context, mQuery);
- KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
KeyItem item = new KeyItem(cursor);
- h.setData(context, item, highlighter);
+ boolean isEnabled = isEnabled(cursor);
+
+ KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
+ h.setData(context, item, highlighter, isEnabled);
}
public boolean isSecretAvailable(int id) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java
index a6cb52977..4ea651bb5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java
@@ -149,11 +149,11 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter {
boolean enabled;
if (cursor.getInt(mIndexIsRevoked) != 0) {
h.statusIcon.setVisibility(View.VISIBLE);
- KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.REVOKED, R.color.bg_gray);
+ KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.REVOKED, R.color.key_flag_gray);
enabled = false;
} else if (cursor.getInt(mIndexIsExpiry) != 0) {
h.statusIcon.setVisibility(View.VISIBLE);
- KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.EXPIRED, R.color.bg_gray);
+ KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.EXPIRED, R.color.key_flag_gray);
enabled = false;
} else {
h.statusIcon.setVisibility(View.GONE);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
index 87539ea05..24f5f04a1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
@@ -284,27 +284,27 @@ public class SubkeysAdapter extends CursorAdapter {
vStatus.setVisibility(View.VISIBLE);
vCertifyIcon.setColorFilter(
- mContext.getResources().getColor(R.color.bg_gray),
+ mContext.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN);
vSignIcon.setColorFilter(
- mContext.getResources().getColor(R.color.bg_gray),
+ mContext.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN);
vEncryptIcon.setColorFilter(
- mContext.getResources().getColor(R.color.bg_gray),
+ mContext.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN);
vAuthenticateIcon.setColorFilter(
- mContext.getResources().getColor(R.color.bg_gray),
+ mContext.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN);
if (isRevoked) {
vStatus.setImageResource(R.drawable.status_signature_revoked_cutout_24dp);
vStatus.setColorFilter(
- mContext.getResources().getColor(R.color.bg_gray),
+ mContext.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN);
} else if (isExpired) {
vStatus.setImageResource(R.drawable.status_signature_expired_cutout_24dp);
vStatus.setColorFilter(
- mContext.getResources().getColor(R.color.bg_gray),
+ mContext.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN);
}
} else {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java
index d1103ac1f..e2c6b0928 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java
@@ -128,7 +128,7 @@ public class UserIdsAdapter extends UserAttributesAdapter {
if (isRevoked) {
// set revocation icon (can this even be primary?)
- KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.REVOKED, R.color.bg_gray);
+ KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.REVOKED, R.color.key_flag_gray);
// disable revoked user ids
vName.setEnabled(false);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java
index 847d76f30..66b784f9b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.ui.base;
import android.app.Activity;
+import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
@@ -29,6 +30,7 @@ import android.view.ViewGroup;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
/**
* Setups Toolbar
@@ -36,14 +38,28 @@ import org.sufficientlysecure.keychain.R;
public abstract class BaseActivity extends AppCompatActivity {
protected Toolbar mToolbar;
protected View mStatusBar;
+ protected ThemeChanger mThemeChanger;
@Override
protected void onCreate(Bundle savedInstanceState) {
+ mThemeChanger = new ThemeChanger(this);
+ mThemeChanger.changeTheme();
super.onCreate(savedInstanceState);
initLayout();
initToolbar();
}
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (mThemeChanger.changeTheme()) {
+ Intent intent = getIntent();
+ finish();
+ startActivity(intent);
+ }
+ }
+
protected void initLayout() {
}
@@ -87,9 +103,7 @@ public abstract class BaseActivity extends AppCompatActivity {
mToolbar.setNavigationOnClickListener(cancelOnClickListener);
}
- /**
- * Close button only
- */
+ /** Close button only */
protected void setFullScreenDialogClose(View.OnClickListener cancelOnClickListener, boolean white) {
if (white) {
setActionBarIcon(R.drawable.ic_close_white_24dp);
@@ -104,6 +118,17 @@ public abstract class BaseActivity extends AppCompatActivity {
setFullScreenDialogClose(cancelOnClickListener, true);
}
+ /** Close button only, with finish-action and given return status, white. */
+ protected void setFullScreenDialogClose(final int result, boolean white) {
+ setFullScreenDialogClose(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setResult(result);
+ finish();
+ }
+ }, white);
+ }
+
/**
* Inflate custom design with two buttons using drawables.
* This does not conform to the Material Design Guidelines, but we deviate here as this is used
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java
index bede16b2a..c472eaa4c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java
@@ -29,7 +29,9 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
+import android.nfc.TagLostException;
import android.nfc.tech.IsoDep;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.Toast;
@@ -63,6 +65,8 @@ public abstract class BaseNfcActivity extends BaseActivity {
public static final int REQUEST_CODE_PIN = 1;
+ public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled";
+
protected Passphrase mPin;
protected Passphrase mAdminPin;
protected boolean mPw1ValidForMultipleSignatures;
@@ -71,13 +75,123 @@ public abstract class BaseNfcActivity extends BaseActivity {
protected boolean mPw3Validated;
private NfcAdapter mNfcAdapter;
private IsoDep mIsoDep;
+ private boolean mTagHandlingEnabled;
private static final int TIMEOUT = 100000;
+ private byte[] mNfcFingerprints;
+ private String mNfcUserId;
+ private byte[] mNfcAid;
+
+ /**
+ * Override to change UI before NFC handling (UI thread)
+ */
+ protected void onNfcPreExecute() {
+ }
+
+ /**
+ * Override to implement NFC operations (background thread)
+ */
+ protected void doNfcInBackground() throws IOException {
+ mNfcFingerprints = nfcGetFingerprints();
+ mNfcUserId = nfcGetUserId();
+ mNfcAid = nfcGetAid();
+ }
+
+ /**
+ * Override to handle result of NFC operations (UI thread)
+ */
+ protected void onNfcPostExecute() throws IOException {
+
+ final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
+
+ try {
+ CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(
+ KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId));
+ long masterKeyId = ring.getMasterKeyId();
+
+ Intent intent = new Intent(this, ViewKeyActivity.class);
+ intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints);
+ startActivity(intent);
+ } catch (PgpKeyNotFoundException e) {
+ Intent intent = new Intent(this, CreateKeyActivity.class);
+ intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, mNfcAid);
+ intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
+ intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints);
+ startActivity(intent);
+ }
+ }
+
+ /**
+ * Override to use something different than Notify (UI thread)
+ */
+ protected void onNfcError(String error) {
+ Notify.create(this, error, Style.WARN).show();
+ }
+
+ public void handleIntentInBackground(final Intent intent) {
+ // Actual NFC operations are executed in doInBackground to not block the UI thread
+ new AsyncTask<Void, Void, Exception>() {
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ onNfcPreExecute();
+ }
+
+ @Override
+ protected Exception doInBackground(Void... params) {
+ try {
+ handleTagDiscoveredIntent(intent);
+ } catch (CardException e) {
+ return e;
+ } catch (IOException e) {
+ return e;
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Exception exception) {
+ super.onPostExecute(exception);
+
+ if (exception != null) {
+ handleNfcError(exception);
+ return;
+ }
+
+ try {
+ onNfcPostExecute();
+ } catch (IOException e) {
+ handleNfcError(e);
+ }
+ }
+ }.execute();
+ }
+
+ protected void pauseTagHandling() {
+ mTagHandlingEnabled = false;
+ }
+
+ protected void resumeTagHandling() {
+ mTagHandlingEnabled = true;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ // Check whether we're recreating a previously destroyed instance
+ if (savedInstanceState != null) {
+ // Restore value of members from saved state
+ mTagHandlingEnabled = savedInstanceState.getBoolean(EXTRA_TAG_HANDLING_ENABLED);
+ } else {
+ mTagHandlingEnabled = true;
+ }
+
Intent intent = getIntent();
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
@@ -86,36 +200,43 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putBoolean(EXTRA_TAG_HANDLING_ENABLED, mTagHandlingEnabled);
+ }
+
/**
* This activity is started as a singleTop activity.
* All new NFC Intents which are delivered to this activity are handled here
*/
@Override
- public void onNewIntent(Intent intent) {
- if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
- try {
- handleTagDiscoveredIntent(intent);
- } catch (CardException e) {
- handleNfcError(e);
- } catch (IOException e) {
- handleNfcError(e);
- }
+ public void onNewIntent(final Intent intent) {
+ if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())
+ && mTagHandlingEnabled) {
+ handleIntentInBackground(intent);
}
}
- public void handleNfcError(IOException e) {
-
+ private void handleNfcError(Exception e) {
Log.e(Constants.TAG, "nfc error", e);
- Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
- }
- public void handleNfcError(CardException e) {
- Log.e(Constants.TAG, "card error", e);
+ if (e instanceof TagLostException) {
+ onNfcError(getString(R.string.error_nfc_tag_lost));
+ return;
+ }
- short status = e.getResponseCode();
+ short status;
+ if (e instanceof CardException) {
+ status = ((CardException) e).getResponseCode();
+ } else {
+ status = -1;
+ }
// When entering a PIN, a status of 63CX indicates X attempts remaining.
if ((status & (short)0xFFF0) == 0x63C0) {
- Notify.create(this, getString(R.string.error_pin, status & 0x000F), Style.WARN).show();
+ int tries = status & 0x000F;
+ onNfcError(getResources().getQuantityString(R.plurals.error_pin, tries, tries));
return;
}
@@ -124,63 +245,62 @@ public abstract class BaseNfcActivity extends BaseActivity {
// These errors should not occur in everyday use; if they are returned, it means we
// made a mistake sending data to the card, or the card is misbehaving.
case 0x6A80: {
- Notify.create(this, getString(R.string.error_nfc_bad_data), Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_bad_data));
break;
}
case 0x6883: {
- Notify.create(this, getString(R.string.error_nfc_chaining_error), Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_chaining_error));
break;
}
case 0x6B00: {
- Notify.create(this, getString(R.string.error_nfc_header, "P1/P2"), Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_header, "P1/P2"));
break;
}
case 0x6D00: {
- Notify.create(this, getString(R.string.error_nfc_header, "INS"), Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_header, "INS"));
break;
}
case 0x6E00: {
- Notify.create(this, getString(R.string.error_nfc_header, "CLA"), Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_header, "CLA"));
break;
}
// These error conditions are more likely to be experienced by an end user.
case 0x6285: {
- Notify.create(this, getString(R.string.error_nfc_terminated), Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_terminated));
break;
}
case 0x6700: {
- Notify.create(this, getString(R.string.error_nfc_wrong_length), Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_wrong_length));
break;
}
case 0x6982: {
- Notify.create(this, getString(R.string.error_nfc_security_not_satisfied),
- Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_security_not_satisfied));
break;
}
case 0x6983: {
- Notify.create(this, getString(R.string.error_nfc_authentication_blocked),
- Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_authentication_blocked));
break;
}
case 0x6985: {
- Notify.create(this, getString(R.string.error_nfc_conditions_not_satisfied),
- Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_conditions_not_satisfied));
break;
}
// 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases.
case 0x6A88:
case 0x6A83: {
- Notify.create(this, getString(R.string.error_nfc_data_not_found), Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_data_not_found));
break;
}
// 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an
// unhandled exception on the smart card.
case 0x6F00: {
- Notify.create(this, getString(R.string.error_nfc_unknown), Style.WARN).show();
+ onNfcError(getString(R.string.error_nfc_unknown));
+ break;
+ }
+ default: {
+ onNfcError(getString(R.string.error_nfc, e.getMessage()));
break;
}
- default:
- Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
}
}
@@ -311,43 +431,12 @@ public abstract class BaseNfcActivity extends BaseActivity {
mPw1ValidatedForDecrypt = false;
mPw3Validated = false;
- // TODO: Handle non-default Admin PIN
- mAdminPin = new Passphrase("12345678");
-
- onNfcPerform();
-
- mIsoDep.close();
- mIsoDep = null;
+ doNfcInBackground();
}
- protected void onNfcPerform() throws IOException {
-
- final byte[] nfcFingerprints = nfcGetFingerprints();
- final String nfcUserId = nfcGetUserId();
- final byte[] nfcAid = nfcGetAid();
-
- final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints);
-
- try {
- CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(
- KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId));
- long masterKeyId = ring.getMasterKeyId();
-
- Intent intent = new Intent(this, ViewKeyActivity.class);
- intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
- startActivity(intent);
- } catch (PgpKeyNotFoundException e) {
- Intent intent = new Intent(this, CreateKeyActivity.class);
- intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, nfcAid);
- intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
- intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
- startActivity(intent);
- }
-
+ public boolean isNfcConnected() {
+ return mIsoDep.isConnected();
}
/** Return the key id from application specific data stored on tag, or null
@@ -569,12 +658,12 @@ public abstract class BaseNfcActivity extends BaseActivity {
*/
public void nfcVerifyPIN(int mode) throws IOException {
if (mPin != null || mode == 0x83) {
- byte[] pin;
+ byte[] pin;
if (mode == 0x83) {
- pin = new String(mAdminPin.getCharArray()).getBytes();
+ pin = mAdminPin.toStringUnsafe().getBytes();
} else {
- pin = new String(mPin.getCharArray()).getBytes();
+ pin = mPin.toStringUnsafe().getBytes();
}
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
@@ -609,14 +698,13 @@ public abstract class BaseNfcActivity extends BaseActivity {
* conformance to the card's requirements for key length.
*
* @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83.
- * @param newPinString The new PW1 or PW3.
+ * @param newPin The new PW1 or PW3.
*/
- public void nfcModifyPIN(int pw, String newPinString) throws IOException {
+ public void nfcModifyPIN(int pw, byte[] newPin) throws IOException {
final int MAX_PW1_LENGTH_INDEX = 1;
final int MAX_PW3_LENGTH_INDEX = 3;
byte[] pwStatusBytes = nfcGetPwStatusBytes();
- byte[] newPin = newPinString.getBytes();
if (pw == 0x81) {
if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) {
@@ -631,11 +719,10 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
byte[] pin;
-
if (pw == 0x83) {
- pin = new String(mAdminPin.getCharArray()).getBytes();
+ pin = mAdminPin.toStringUnsafe().getBytes();
} else {
- pin = new String(mPin.getCharArray()).getBytes();
+ pin = mPin.toStringUnsafe().getBytes();
}
// Command APDU for CHANGE REFERENCE DATA command (page 32)
@@ -700,7 +787,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
throw new IOException("Invalid key slot");
}
- RSAPrivateCrtKey crtSecretKey = null;
+ RSAPrivateCrtKey crtSecretKey;
try {
secretKey.unlock(passphrase);
crtSecretKey = secretKey.getCrtSecretKey();
@@ -719,7 +806,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
if (!mPw3Validated) {
- nfcVerifyPIN(0x83); // (Verify PW1 with mode 83)
+ nfcVerifyPIN(0x83); // (Verify PW3 with mode 83)
}
byte[] header= Hex.decode(
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java
index 17e4e6ede..06361e8cb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java
@@ -8,7 +8,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
public abstract class CachingCryptoOperationFragment <T extends Parcelable, S extends OperationResult>
- extends CryptoOperationFragment<T, S> {
+ extends QueueingCryptoOperationFragment<T, S> {
public static final String ARG_CACHED_ACTIONS = "cached_actions";
@@ -31,8 +31,12 @@ public abstract class CachingCryptoOperationFragment <T extends Parcelable, S ex
}
@Override
- protected void onCryptoOperationResult(S result) {
- super.onCryptoOperationResult(result);
+ public void onQueuedOperationSuccess(S result) {
+ mCachedActionsParcel = null;
+ }
+
+ @Override
+ public void onQueuedOperationError(S result) {
mCachedActionsParcel = null;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java
index 19d808ba6..52507f3e9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java
@@ -18,78 +18,112 @@
package org.sufficientlysecure.keychain.ui.base;
+import android.content.Context;
import android.content.Intent;
+import android.os.Bundle;
import android.os.Parcelable;
+import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
-/**
- * All fragments executing crypto operations need to extend this class.
+/** This is a base class for fragments which implement a cryptoOperation.
+ *
+ * Subclasses of this class can call the cryptoOperation method to execute an
+ * operation in KeychainService which takes a parcelable of type T as its input
+ * and returns an OperationResult of type S as a result.
+ *
+ * The input (of type T) is not given directly to the cryptoOperation method,
+ * but must be provided by the overriden createOperationInput method to be
+ * available upon request during execution of the cryptoOperation.
+ *
+ * After running cryptoOperation, one of the onCryptoOperation*() methods will
+ * be called, depending on the success status of the operation. The subclass
+ * must override at least onCryptoOperationSuccess to proceed after a
+ * successful operation.
+ *
+ * @see KeychainService
+ *
*/
-public abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult>
+abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult>
extends Fragment implements CryptoOperationHelper.Callback<T, S> {
- private CryptoOperationHelper<T, S> mOperationHelper;
-
- public CryptoOperationFragment() {
+ final private CryptoOperationHelper<T, S> mOperationHelper;
- mOperationHelper = new CryptoOperationHelper<>(this, this);
+ public CryptoOperationFragment(Integer initialProgressMsg) {
+ mOperationHelper = new CryptoOperationHelper<>(1, this, this, initialProgressMsg);
}
- public void setProgressMessageResource(int id) {
- mOperationHelper.setProgressMessageResource(id);
+ public CryptoOperationFragment() {
+ mOperationHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_processing);
}
-
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mOperationHelper.handleActivityResult(requestCode, resultCode, data);
}
- @Override
- public abstract T createOperationInput();
-
+ /** Starts execution of the cryptographic operation.
+ *
+ * During this process, the createOperationInput() method will be called,
+ * this input will be handed to KeychainService, where it is executed in
+ * the appropriate *Operation class. If the result is a PendingInputResult,
+ * it is handled accordingly. Otherwise, it is returned in one of the
+ * onCryptoOperation* callbacks.
+ */
protected void cryptoOperation() {
- cryptoOperation(new CryptoInputParcel());
+ mOperationHelper.cryptoOperation();
}
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
- cryptoOperation(cryptoInput);
+ mOperationHelper.cryptoOperation(cryptoInput);
}
- protected void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) {
- mOperationHelper.cryptoOperation(cryptoInput, showProgress);
- }
+ @Override @Nullable
+ /** Creates input for the crypto operation. Called internally after the
+ * crypto operation is started by a call to cryptoOperation(). Silently
+ * cancels operation if this method returns null. */
+ public abstract T createOperationInput();
+ /** Returns false, indicating that we did not handle progress ourselves. */
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return false;
}
- @Override
- public void onCryptoOperationError(S result) {
- onCryptoOperationResult(result);
- result.createNotify(getActivity()).show();
+ public void setProgressMessageResource(int id) {
+ mOperationHelper.setProgressMessageResource(id);
}
@Override
- public void onCryptoOperationCancelled() {
- }
+ /** Called when the cryptoOperation() was successful. No default behavior
+ * here, this should always be implemented by a subclass! */
+ abstract public void onCryptoOperationSuccess(S result);
@Override
- public void onCryptoOperationSuccess(S result) {
- onCryptoOperationResult(result);
+ abstract public void onCryptoOperationError(S result);
+
+ @Override
+ public void onCryptoOperationCancelled() {
}
- /**
- *
- * To be overriden by subclasses, if desired. Provides a way to access the method by the
- * same name in CryptoOperationHelper, if super.onCryptoOperationSuccess and
- * super.onCryptoOperationError are called at the start of the respective functions in the
- * subclass overriding them
- * @param result
- */
- protected void onCryptoOperationResult(S result) {
+ public void hideKeyboard() {
+ if (getActivity() == null) {
+ return;
+ }
+ InputMethodManager inputManager = (InputMethodManager) getActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ // check if no view has focus
+ View v = getActivity().getCurrentFocus();
+ if (v == null)
+ return;
+
+ inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
index 240dd0972..b33128978 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
@@ -28,10 +28,9 @@ import android.os.Messenger;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
-
import android.support.v4.app.FragmentManager;
+
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.service.KeychainService;
@@ -39,7 +38,9 @@ import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
+import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity;
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
+import org.sufficientlysecure.keychain.ui.RetryUploadDialogActivity;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
@@ -52,23 +53,33 @@ import org.sufficientlysecure.keychain.util.Log;
*/
public class CryptoOperationHelper<T extends Parcelable, S extends OperationResult> {
- public interface Callback <T extends Parcelable, S extends OperationResult> {
+ public interface Callback<T extends Parcelable, S extends OperationResult> {
T createOperationInput();
+
void onCryptoOperationSuccess(S result);
+
void onCryptoOperationCancelled();
+
void onCryptoOperationError(S result);
+
boolean onCryptoSetProgress(String msg, int progress, int max);
}
- public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
- public static final int REQUEST_CODE_NFC = 0x00008002;
+ // request codes from CryptoOperationHelper are created essentially
+ // a static property, used to identify requestCodes meant for this
+ // particular helper. a request code looks as follows:
+ // (id << 9) + (1<<8) + REQUEST_CODE_X
+ // that is, starting from LSB, there are 8 bits request code, 1
+ // fixed bit set, then 7 bit operator-id code. the first two
+ // summands are stored in the mId for easy operation.
+ private final int mId;
- // keeps track of request code used to start an activity from this CryptoOperationHelper.
- // this is necessary when multiple CryptoOperationHelpers are used in the same fragment/activity
- // otherwise all CryptoOperationHandlers may respond to the same onActivityResult
- private int mRequestedCode = -1;
+ public static final int REQUEST_CODE_PASSPHRASE = 1;
+ public static final int REQUEST_CODE_NFC = 2;
+ public static final int REQUEST_CODE_ENABLE_ORBOT = 3;
+ public static final int REQUEST_CODE_RETRY_UPLOAD = 4;
- private int mProgressMessageResource;
+ private Integer mProgressMessageResource;
private FragmentActivity mActivity;
private Fragment mFragment;
@@ -78,10 +89,10 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
/**
* If OperationHelper is being integrated into an activity
- *
- * @param activity
*/
- public CryptoOperationHelper(FragmentActivity activity, Callback<T, S> callback, int progressMessageString) {
+ public CryptoOperationHelper(int id, FragmentActivity activity, Callback<T, S> callback,
+ Integer progressMessageString) {
+ mId = (id << 9) + (1<<8);
mActivity = activity;
mUseFragment = false;
mCallback = callback;
@@ -90,48 +101,33 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
/**
* if OperationHelper is being integrated into a fragment
- *
- * @param fragment
*/
- public CryptoOperationHelper(Fragment fragment, Callback<T, S> callback, int progressMessageString) {
+ public CryptoOperationHelper(int id, Fragment fragment, Callback<T, S> callback, Integer progressMessageString) {
+ mId = (id << 9) + (1<<8);
mFragment = fragment;
mUseFragment = true;
mProgressMessageResource = progressMessageString;
mCallback = callback;
}
- /**
- * if OperationHelper is being integrated into a fragment with default message for the progress dialog
- *
- * @param fragment
- */
- public CryptoOperationHelper(Fragment fragment, Callback<T, S> callback) {
- mFragment = fragment;
- mUseFragment = true;
- mProgressMessageResource = R.string.progress_building_key;
- mCallback = callback;
- }
-
public void setProgressMessageResource(int id) {
mProgressMessageResource = id;
}
- private void initiateInputActivity(RequiredInputParcel requiredInput) {
+ private void initiateInputActivity(RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInputParcel) {
Activity activity = mUseFragment ? mFragment.getActivity() : mActivity;
switch (requiredInput.mType) {
+ // always use CryptoOperationHelper.startActivityForResult!
case NFC_MOVE_KEY_TO_CARD:
case NFC_DECRYPT:
case NFC_SIGN: {
Intent intent = new Intent(activity, NfcOperationActivity.class);
intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
- mRequestedCode = REQUEST_CODE_NFC;
- if (mUseFragment) {
- mFragment.startActivityForResult(intent, mRequestedCode);
- } else {
- mActivity.startActivityForResult(intent, mRequestedCode);
- }
+ intent.putExtra(NfcOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
+ startActivityForResult(intent, REQUEST_CODE_NFC);
return;
}
@@ -139,37 +135,55 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
case PASSPHRASE_SYMMETRIC: {
Intent intent = new Intent(activity, PassphraseDialogActivity.class);
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
- mRequestedCode = REQUEST_CODE_PASSPHRASE;
- if (mUseFragment) {
- mFragment.startActivityForResult(intent, mRequestedCode);
- } else {
- mActivity.startActivityForResult(intent, mRequestedCode);
- }
+ intent.putExtra(PassphraseDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
+ startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
return;
}
+
+ case ENABLE_ORBOT: {
+ Intent intent = new Intent(activity, OrbotRequiredDialogActivity.class);
+ intent.putExtra(OrbotRequiredDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
+ startActivityForResult(intent, REQUEST_CODE_ENABLE_ORBOT);
+ return;
+ }
+
+ case UPLOAD_FAIL_RETRY: {
+ Intent intent = new Intent(activity, RetryUploadDialogActivity.class);
+ intent.putExtra(RetryUploadDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
+ startActivityForResult(intent, REQUEST_CODE_RETRY_UPLOAD);
+ return;
+ }
+
+ default: {
+ throw new RuntimeException("Unhandled pending result!");
+ }
}
+ }
- throw new RuntimeException("Unhandled pending result!");
+ protected void startActivityForResult(Intent intent, int requestCode) {
+ if (mUseFragment) {
+ mFragment.startActivityForResult(intent, mId + requestCode);
+ } else {
+ mActivity.startActivityForResult(intent, mId + requestCode);
+ }
}
/**
- * Attempts the result of an activity started by this helper. Returns true if requestCode is recognized,
- * false otherwise.
- *
- * @param requestCode
- * @param resultCode
- * @param data
+ * Attempts the result of an activity started by this helper. Returns true if requestCode is
+ * recognized, false otherwise.
* @return true if requestCode was recognized, false otherwise
*/
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(Constants.TAG, "received activity result in OperationHelper");
- if (mRequestedCode != requestCode) {
+ if ((requestCode & mId) != mId) {
// this wasn't meant for us to handle
return false;
- } else {
- mRequestedCode = -1;
}
+ Log.d(Constants.TAG, "handling activity result in OperationHelper");
+ // filter out mId from requestCode
+ requestCode ^= mId;
+
if (resultCode == Activity.RESULT_CANCELED) {
mCallback.onCryptoOperationCancelled();
return true;
@@ -181,7 +195,6 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
CryptoInputParcel cryptoInput =
data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
cryptoOperation(cryptoInput);
- return true;
}
break;
}
@@ -189,17 +202,33 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
case REQUEST_CODE_NFC: {
if (resultCode == Activity.RESULT_OK && data != null) {
CryptoInputParcel cryptoInput =
- data.getParcelableExtra(NfcOperationActivity.RESULT_DATA);
+ data.getParcelableExtra(NfcOperationActivity.RESULT_CRYPTO_INPUT);
cryptoOperation(cryptoInput);
- return true;
}
break;
}
- default: {
- return false;
+ case REQUEST_CODE_ENABLE_ORBOT: {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ CryptoInputParcel cryptoInput =
+ data.getParcelableExtra(
+ OrbotRequiredDialogActivity.RESULT_CRYPTO_INPUT);
+ cryptoOperation(cryptoInput);
+ }
+ break;
+ }
+
+ case REQUEST_CODE_RETRY_UPLOAD: {
+ if (resultCode == Activity.RESULT_OK) {
+ CryptoInputParcel cryptoInput =
+ data.getParcelableExtra(
+ RetryUploadDialogActivity.RESULT_CRYPTO_INPUT);
+ cryptoOperation(cryptoInput);
+ }
+ break;
}
}
+
return true;
}
@@ -225,7 +254,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
}
- public void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) {
+ public void cryptoOperation(final CryptoInputParcel cryptoInput) {
FragmentActivity activity = mUseFragment ? mFragment.getActivity() : mActivity;
@@ -264,7 +293,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
@Override
protected void onSetProgress(String msg, int progress, int max) {
// allow handling of progress in fragment, or delegate upwards
- if ( ! mCallback.onCryptoSetProgress(msg, progress, max)) {
+ if (!mCallback.onCryptoSetProgress(msg, progress, max)) {
super.onSetProgress(msg, progress, max);
}
}
@@ -274,7 +303,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
- if (showProgress) {
+ if (mProgressMessageResource != null) {
saveHandler.showProgressDialog(
activity.getString(mProgressMessageResource),
ProgressDialog.STYLE_HORIZONTAL, false);
@@ -283,31 +312,18 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
activity.startService(intent);
}
- public void cryptoOperation(CryptoInputParcel cryptoInputParcel) {
- cryptoOperation(cryptoInputParcel, true);
- }
-
public void cryptoOperation() {
cryptoOperation(new CryptoInputParcel());
}
- protected void onCryptoOperationResult(S result) {
- if (result.success()) {
- mCallback.onCryptoOperationSuccess(result);
- } else {
- mCallback.onCryptoOperationError(result);
- }
- }
-
public void onHandleResult(OperationResult result) {
Log.d(Constants.TAG, "Handling result in OperationHelper success: " + result.success());
if (result instanceof InputPendingResult) {
InputPendingResult pendingResult = (InputPendingResult) result;
if (pendingResult.isPending()) {
-
RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel();
- initiateInputActivity(requiredInput);
+ initiateInputActivity(requiredInput, pendingResult.mCryptoInputParcel);
return;
}
}
@@ -315,12 +331,16 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
dismissProgress();
try {
- // noinspection unchecked, because type erasure :(
- onCryptoOperationResult((S) result);
+ if (result.success()) {
+ // noinspection unchecked, because type erasure :(
+ mCallback.onCryptoOperationSuccess((S) result);
+ } else {
+ // noinspection unchecked, because type erasure :(
+ mCallback.onCryptoOperationError((S) result);
+ }
} catch (ClassCastException e) {
throw new AssertionError("bad return class ("
+ result.getClass().getSimpleName() + "), this is a programming error!");
}
-
}
} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/QueueingCryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/QueueingCryptoOperationFragment.java
new file mode 100644
index 000000000..65e0ce941
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/QueueingCryptoOperationFragment.java
@@ -0,0 +1,93 @@
+package org.sufficientlysecure.keychain.ui.base;
+
+
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
+
+
+/** CryptoOperationFragment which calls crypto operation results only while
+ * attached to Activity.
+ *
+ * This subclass of CryptoOperationFragment substitutes the onCryptoOperation*
+ * methods for onQueuedOperation* ones, which are ensured to be called while
+ * the fragment is attached to an Activity, possibly delaying the call until
+ * the Fragment is re-attached.
+ *
+ * TODO merge this functionality into CryptoOperationFragment?
+ *
+ * @see CryptoOperationFragment
+ */
+public abstract class QueueingCryptoOperationFragment<T extends Parcelable, S extends OperationResult>
+ extends CryptoOperationFragment<T,S> {
+
+ public static final String ARG_QUEUED_RESULT = "queued_result";
+ private S mQueuedResult;
+
+ public QueueingCryptoOperationFragment() {
+ super();
+ }
+
+ public QueueingCryptoOperationFragment(Integer initialProgressMsg) {
+ super(initialProgressMsg);
+ }
+
+ @Override
+ public void onViewStateRestored(Bundle savedInstanceState) {
+ super.onViewStateRestored(savedInstanceState);
+
+ if (mQueuedResult != null) {
+ try {
+ if (mQueuedResult.success()) {
+ onQueuedOperationSuccess(mQueuedResult);
+ } else {
+ onQueuedOperationError(mQueuedResult);
+ }
+ } finally {
+ mQueuedResult = null;
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putParcelable(ARG_QUEUED_RESULT, mQueuedResult);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ mQueuedResult = savedInstanceState.getParcelable(ARG_QUEUED_RESULT);
+ }
+ }
+
+ public abstract void onQueuedOperationSuccess(S result);
+
+ public void onQueuedOperationError(S result) {
+ hideKeyboard();
+ result.createNotify(getActivity()).show();
+ }
+
+ @Override
+ final public void onCryptoOperationSuccess(S result) {
+ if (getActivity() == null) {
+ mQueuedResult = result;
+ return;
+ }
+ onQueuedOperationSuccess(result);
+ }
+
+ @Override
+ final public void onCryptoOperationError(S result) {
+ if (getActivity() == null) {
+ mQueuedResult = result;
+ return;
+ }
+ onQueuedOperationError(result);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java
index 2c1714b67..691cc009d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java
@@ -24,7 +24,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import android.app.Activity;
-import android.app.AlertDialog;
+import android.support.v7.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
@@ -48,13 +48,23 @@ import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.TlsHelper;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
-public class AddKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener {
- private static final String ARG_MESSENGER = "messenger";
+import java.net.Proxy;
+
+public class AddEditKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener {
+ private static final String ARG_MESSENGER = "arg_messenger";
+ private static final String ARG_ACTION = "arg_dialog_action";
+ private static final String ARG_POSITION = "arg_position";
+ private static final String ARG_KEYSERVER = "arg_keyserver";
public static final int MESSAGE_OKAY = 1;
public static final int MESSAGE_VERIFICATION_FAILED = 2;
@@ -62,20 +72,37 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
public static final String MESSAGE_KEYSERVER = "new_keyserver";
public static final String MESSAGE_VERIFIED = "verified";
public static final String MESSAGE_FAILURE_REASON = "failure_reason";
+ public static final String MESSAGE_KEYSERVER_DELETED = "keyserver_deleted";
+ public static final String MESSAGE_DIALOG_ACTION = "message_dialog_action";
+ public static final String MESSAGE_EDIT_POSITION = "keyserver_edited_position";
private Messenger mMessenger;
+ private DialogAction mDialogAction;
+ private int mPosition;
+
private EditText mKeyserverEditText;
private CheckBox mVerifyKeyserverCheckBox;
+ public enum DialogAction {
+ ADD,
+ EDIT
+ }
+
public enum FailureReason {
INVALID_URL,
CONNECTION_FAILED
}
- public static AddKeyserverDialogFragment newInstance(Messenger messenger) {
- AddKeyserverDialogFragment frag = new AddKeyserverDialogFragment();
+ public static AddEditKeyserverDialogFragment newInstance(Messenger messenger,
+ DialogAction action,
+ String keyserver,
+ int position) {
+ AddEditKeyserverDialogFragment frag = new AddEditKeyserverDialogFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_MESSENGER, messenger);
+ args.putSerializable(ARG_ACTION, action);
+ args.putString(ARG_KEYSERVER, keyserver);
+ args.putInt(ARG_POSITION, position);
frag.setArguments(args);
@@ -88,11 +115,11 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
final Activity activity = getActivity();
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
+ mDialogAction = (DialogAction) getArguments().getSerializable(ARG_ACTION);
+ mPosition = getArguments().getInt(ARG_POSITION);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
- alert.setTitle(R.string.add_keyserver_dialog_title);
-
LayoutInflater inflater = activity.getLayoutInflater();
View view = inflater.inflate(R.layout.add_keyserver_dialog, null);
alert.setView(view);
@@ -100,14 +127,26 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
mKeyserverEditText = (EditText) view.findViewById(R.id.keyserver_url_edit_text);
mVerifyKeyserverCheckBox = (CheckBox) view.findViewById(R.id.verify_keyserver_checkbox);
- // we don't want dialog to be dismissed on click, thereby requiring the hack seen below
- // and in onStart
+ switch (mDialogAction) {
+ case ADD: {
+ alert.setTitle(R.string.add_keyserver_dialog_title);
+ break;
+ }
+ case EDIT: {
+ alert.setTitle(R.string.edit_keyserver_dialog_title);
+ mKeyserverEditText.setText(getArguments().getString(ARG_KEYSERVER));
+ break;
+ }
+ }
+
+ // we don't want dialog to be dismissed on click for keyserver addition or edit,
+ // thereby requiring the hack seen below and in onStart
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
// we need to have an empty listener to prevent errors on some devices as mentioned
// at http://stackoverflow.com/q/13746412/3000919
- // actual listener set in onStart
+ // actual listener set in onStart for adding keyservers or editing them
}
});
@@ -119,6 +158,23 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
}
});
+ switch (mDialogAction) {
+ case EDIT: {
+ alert.setNeutralButton(R.string.label_keyserver_dialog_delete,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ deleteKeyserver(mPosition);
+ }
+ });
+ break;
+ }
+ case ADD: {
+ // do nothing
+ break;
+ }
+ }
+
// Hack to open keyboard.
// This is the only method that I found to work across all Android versions
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
@@ -155,25 +211,53 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- String keyserverUrl = mKeyserverEditText.getText().toString();
+ // behaviour same for edit and add
+ final String keyserverUrl = mKeyserverEditText.getText().toString();
if (mVerifyKeyserverCheckBox.isChecked()) {
- verifyConnection(keyserverUrl);
+ final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity())
+ .getProxyPrefs();
+ Runnable ignoreTor = new Runnable() {
+ @Override
+ public void run() {
+ verifyConnection(keyserverUrl, null);
+ }
+ };
+
+ if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs,
+ getActivity())) {
+ verifyConnection(keyserverUrl, proxyPrefs.parcelableProxy.getProxy());
+ }
} else {
dismiss();
// return unverified keyserver back to activity
- addKeyserver(keyserverUrl, false);
+ keyserverEdited(keyserverUrl, false);
}
}
});
}
}
- public void addKeyserver(String keyserver, boolean verified) {
+ public void keyserverEdited(String keyserver, boolean verified) {
dismiss();
Bundle data = new Bundle();
+ data.putSerializable(MESSAGE_DIALOG_ACTION, mDialogAction);
data.putString(MESSAGE_KEYSERVER, keyserver);
data.putBoolean(MESSAGE_VERIFIED, verified);
+ if (mDialogAction == DialogAction.EDIT) {
+ data.putInt(MESSAGE_EDIT_POSITION, mPosition);
+ }
+
+ sendMessageToHandler(MESSAGE_OKAY, data);
+ }
+
+ public void deleteKeyserver(int position) {
+ dismiss();
+ Bundle data = new Bundle();
+ data.putSerializable(MESSAGE_DIALOG_ACTION, DialogAction.EDIT);
+ data.putInt(MESSAGE_EDIT_POSITION, position);
+ data.putBoolean(MESSAGE_KEYSERVER_DELETED, true);
+
sendMessageToHandler(MESSAGE_OKAY, data);
}
@@ -184,7 +268,7 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
sendMessageToHandler(MESSAGE_VERIFICATION_FAILED, data);
}
- public void verifyConnection(String keyserver) {
+ public void verifyConnection(String keyserver, final Proxy proxy) {
new AsyncTask<String, Void, FailureReason>() {
ProgressDialog mProgressDialog;
@@ -218,10 +302,11 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
}
URI newKeyserver = new URI(scheme, schemeSpecificPart, fragment);
- Log.d(Constants.TAG, "Converted URL" + newKeyserver);
+ Log.d("Converted URL", newKeyserver.toString());
- // just see if we can get a connection, then immediately close
- TlsHelper.openConnection(newKeyserver.toURL()).getInputStream().close();
+ OkHttpClient client = HkpKeyserver.getClient(newKeyserver.toURL(), proxy);
+ TlsHelper.pinCertificateIfNecessary(client, newKeyserver.toURL());
+ client.newCall(new Request.Builder().url(newKeyserver.toURL()).build()).execute();
} catch (TlsHelper.TlsHelperException e) {
reason = FailureReason.CONNECTION_FAILED;
} catch (MalformedURLException | URISyntaxException e) {
@@ -238,7 +323,7 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
protected void onPostExecute(FailureReason failureReason) {
mProgressDialog.dismiss();
if (failureReason == null) {
- addKeyserver(mKeyserver, true);
+ keyserverEdited(mKeyserver, true);
} else {
verificationFailed(failureReason);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java
index 5b91b9d37..c55c75b55 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java
@@ -18,7 +18,7 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Activity;
-import android.app.AlertDialog;
+import android.support.v7.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java
index 0b1d39fc1..b51d081e1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java
@@ -18,12 +18,12 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.annotation.TargetApi;
-import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
+import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java
index fe4ba0262..bc82feb70 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java
@@ -18,7 +18,7 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Activity;
-import android.app.AlertDialog;
+import android.support.v7.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java
index d2fa37cf7..d96879119 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java
@@ -27,7 +27,7 @@ import org.sufficientlysecure.keychain.R;
public class AdvancedAppSettingsDialogFragment extends DialogFragment {
private static final String ARG_PACKAGE_NAME = "package_name";
- private static final String ARG_SIGNATURE = "signature";
+ private static final String ARG_CERTIFICATE = "certificate";
/**
* Creates new instance of this fragment
@@ -36,7 +36,7 @@ public class AdvancedAppSettingsDialogFragment extends DialogFragment {
AdvancedAppSettingsDialogFragment frag = new AdvancedAppSettingsDialogFragment();
Bundle args = new Bundle();
args.putString(ARG_PACKAGE_NAME, packageName);
- args.putString(ARG_SIGNATURE, digest);
+ args.putString(ARG_CERTIFICATE, digest);
frag.setArguments(args);
return frag;
@@ -62,10 +62,10 @@ public class AdvancedAppSettingsDialogFragment extends DialogFragment {
});
String packageName = getArguments().getString(ARG_PACKAGE_NAME);
- String signature = getArguments().getString(ARG_SIGNATURE);
+ String certificate = getArguments().getString(ARG_CERTIFICATE);
alert.setMessage(getString(R.string.api_settings_package_name) + ": " + packageName + "\n\n"
- + getString(R.string.api_settings_package_signature) + ": " + signature);
+ + getString(R.string.api_settings_package_certificate) + ": " + certificate);
return alert.show();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java
index 794af5b15..840b95a3c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java
@@ -17,42 +17,15 @@
package org.sufficientlysecure.keychain.ui.dialog;
-import android.app.AlertDialog;
import android.content.Context;
-import android.view.View;
-import android.widget.TextView;
-
-import org.sufficientlysecure.keychain.R;
+import android.support.v7.app.AlertDialog;
/**
- * This class extends AlertDiaog.Builder, styling the header using emphasis color.
- * Note that this class is a huge hack, because dialog boxes aren't easily stylable.
- * Also, the dialog NEEDS to be called with show() directly, not create(), otherwise
- * the order of internal operations will lead to a crash!
+ * Uses support lib's dialog builder. We can apply a theme here later!
*/
public class CustomAlertDialogBuilder extends AlertDialog.Builder {
public CustomAlertDialogBuilder(Context context) {
super(context);
}
-
- @Override
- public AlertDialog show() {
- AlertDialog dialog = super.show();
-
- int dividerId = dialog.getContext().getResources().getIdentifier("android:id/titleDivider", null, null);
- View divider = dialog.findViewById(dividerId);
- if (divider != null) {
- divider.setBackgroundColor(dialog.getContext().getResources().getColor(R.color.header_text));
- }
-
- int textViewId = dialog.getContext().getResources().getIdentifier("android:id/alertTitle", null, null);
- TextView tv = (TextView) dialog.findViewById(textViewId);
- if (tv != null) {
- tv.setTextColor(dialog.getContext().getResources().getColor(R.color.header_text));
- }
-
- return dialog;
- }
-
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java
deleted file mode 100644
index 7fc9da9ec..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2013-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.dialog;
-
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.support.v4.app.DialogFragment;
-import android.support.v4.app.FragmentActivity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextView;
-
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.operations.results.DeleteResult;
-import org.sufficientlysecure.keychain.operations.results.OperationResult;
-import org.sufficientlysecure.keychain.pgp.KeyRing;
-import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.service.DeleteKeyringParcel;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
-import org.sufficientlysecure.keychain.util.Log;
-
-import java.util.HashMap;
-
-public class DeleteKeyDialogFragment extends DialogFragment
- implements CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult> {
- private static final String ARG_MESSENGER = "messenger";
- private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids";
-
- public static final int MESSAGE_OKAY = 1;
- public static final int MESSAGE_ERROR = 0;
-
- private TextView mMainMessage;
- private View mInflateView;
-
- private Messenger mMessenger;
-
- // for CryptoOperationHelper.Callback
- private long[] mMasterKeyIds;
- private boolean mHasSecret;
- private CryptoOperationHelper<DeleteKeyringParcel, DeleteResult> mDeleteOpHelper;
-
- /**
- * Creates new instance of this delete file dialog fragment
- */
- public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] masterKeyIds) {
- DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
- Bundle args = new Bundle();
-
- args.putParcelable(ARG_MESSENGER, messenger);
- args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds);
-
- frag.setArguments(args);
-
- return frag;
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (mDeleteOpHelper != null) {
- mDeleteOpHelper.handleActivityResult(requestCode, resultCode, data);
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final FragmentActivity activity = getActivity();
- mMessenger = getArguments().getParcelable(ARG_MESSENGER);
-
- final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS);
-
- CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(activity);
-
- // Setup custom View to display in AlertDialog
- LayoutInflater inflater = activity.getLayoutInflater();
- mInflateView = inflater.inflate(R.layout.view_key_delete_fragment, null);
- builder.setView(mInflateView);
-
- mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage);
-
- final boolean hasSecret;
-
- // If only a single key has been selected
- if (masterKeyIds.length == 1) {
- long masterKeyId = masterKeyIds[0];
-
- try {
- HashMap<String, Object> data = new ProviderHelper(activity).getUnifiedData(
- masterKeyId, new String[]{
- KeyRings.USER_ID,
- KeyRings.HAS_ANY_SECRET
- }, new int[]{
- ProviderHelper.FIELD_TYPE_STRING,
- ProviderHelper.FIELD_TYPE_INTEGER
- }
- );
- String name;
- KeyRing.UserId mainUserId = KeyRing.splitUserId((String) data.get(KeyRings.USER_ID));
- if (mainUserId.name != null) {
- name = mainUserId.name;
- } else {
- name = getString(R.string.user_id_no_name);
- }
- hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1;
-
- if (hasSecret) {
- // show title only for secret key deletions,
- // see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior
- builder.setTitle(getString(R.string.title_delete_secret_key, name));
- mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name));
- } else {
- mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name));
- }
- } catch (ProviderHelper.NotFoundException e) {
- dismiss();
- return null;
- }
- } else {
- mMainMessage.setText(R.string.key_deletion_confirmation_multi);
- hasSecret = false;
- }
-
- builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
-
- mMasterKeyIds = masterKeyIds;
- mHasSecret = hasSecret;
-
- mDeleteOpHelper = new CryptoOperationHelper<>
- (DeleteKeyDialogFragment.this, DeleteKeyDialogFragment.this,
- R.string.progress_deleting);
- mDeleteOpHelper.cryptoOperation();
- // do NOT dismiss here, it'll give
- // OperationHelper a null fragmentManager
- // dismiss();
- }
- });
- builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dismiss();
- }
- });
-
- return builder.show();
- }
-
- @Override
- public DeleteKeyringParcel createOperationInput() {
- return new DeleteKeyringParcel(mMasterKeyIds, mHasSecret);
- }
-
- @Override
- public void onCryptoOperationSuccess(DeleteResult result) {
- handleResult(result);
- }
-
- @Override
- public void onCryptoOperationCancelled() {
-
- }
-
- @Override
- public void onCryptoOperationError(DeleteResult result) {
- handleResult(result);
- }
-
- @Override
- public boolean onCryptoSetProgress(String msg, int progress, int max) {
- return false;
- }
-
- public void handleResult(DeleteResult result) {
- try {
- Bundle data = new Bundle();
- data.putParcelable(OperationResult.EXTRA_RESULT, result);
- Message msg = Message.obtain();
- msg.arg1 = ServiceProgressHandler.MessageStatus.OKAY.ordinal();
- msg.setData(data);
- mMessenger.send(msg);
- } catch (RemoteException e) {
- Log.e(Constants.TAG, "messenger error", e);
- }
- dismiss();
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java
index eafa129f0..b51648740 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java
@@ -35,7 +35,7 @@ public class EditSubkeyDialogFragment extends DialogFragment {
public static final int MESSAGE_CHANGE_EXPIRY = 1;
public static final int MESSAGE_REVOKE = 2;
public static final int MESSAGE_STRIP = 3;
- public static final int MESSAGE_KEYTOCARD = 4;
+ public static final int MESSAGE_MOVE_KEY_TO_CARD = 4;
private Messenger mMessenger;
@@ -78,7 +78,7 @@ public class EditSubkeyDialogFragment extends DialogFragment {
sendMessageToHandler(MESSAGE_STRIP, null);
break;
case 3:
- sendMessageToHandler(MESSAGE_KEYTOCARD, null);
+ sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null);
break;
default:
break;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java
index 37e05a61d..c9fb990a2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java
@@ -24,6 +24,7 @@ import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
+import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
@@ -36,6 +37,8 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Calendar;
@@ -72,17 +75,19 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment {
/**
* Creates dialog
*/
+ @NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Activity activity = getActivity();
+ Activity activity = getActivity();
+
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
long creation = getArguments().getLong(ARG_CREATION);
long expiry = getArguments().getLong(ARG_EXPIRY);
- Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
- creationCal.setTime(new Date(creation * 1000));
- final Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
- expiryCal.setTime(new Date(expiry * 1000));
+ final Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ creationCal.setTimeInMillis(creation * 1000);
+ Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ expiryCal.setTimeInMillis(expiry * 1000);
// date picker works with default time zone, we need to convert from UTC to default timezone
creationCal.setTimeZone(TimeZone.getDefault());
@@ -123,11 +128,8 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment {
noExpiry.setChecked(false);
expiryLayout.setVisibility(View.VISIBLE);
- // convert from UTC to time zone of device
- Calendar expiryCalTimeZone = (Calendar) expiryCal.clone();
- expiryCalTimeZone.setTimeZone(TimeZone.getDefault());
currentExpiry.setText(DateFormat.getDateFormat(
- getActivity()).format(expiryCalTimeZone.getTime()));
+ getActivity()).format(expiryCal.getTime()));
}
// date picker works based on default time zone
@@ -175,10 +177,13 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment {
selectedCal.setTimeZone(TimeZone.getTimeZone("UTC"));
long numDays = (selectedCal.getTimeInMillis() / 86400000)
- - (expiryCal.getTimeInMillis() / 86400000);
+ - (creationCal.getTimeInMillis() / 86400000);
if (numDays <= 0) {
- Log.e(Constants.TAG, "Should not happen! Expiry num of days <= 0!");
- throw new RuntimeException();
+ Activity activity = getActivity();
+ if (activity != null) {
+ Notify.create(activity, R.string.error_expiry_past, Style.ERROR).show();
+ }
+ return;
}
expiry = selectedCal.getTime().getTime() / 1000;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java
index 63b6d26ac..5ef8618ce 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java
@@ -136,6 +136,7 @@ public class FileDialogFragment extends DialogFragment {
mCheckBox.setEnabled(true);
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setText(checkboxText);
+ mCheckBox.setChecked(true);
}
alert.setView(view);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java
new file mode 100644
index 000000000..d1d22b6d7
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012-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.dialog;
+
+import android.app.Activity;
+import android.support.v7.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.v4.app.DialogFragment;
+import android.view.ContextThemeWrapper;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
+
+/**
+ * displays a dialog asking the user to enable Tor
+ */
+public class OrbotStartDialogFragment extends DialogFragment {
+ private static final String ARG_MESSENGER = "messenger";
+ private static final String ARG_TITLE = "title";
+ private static final String ARG_MESSAGE = "message";
+ private static final String ARG_MIDDLE_BUTTON = "middleButton";
+
+ public static final int MESSAGE_MIDDLE_BUTTON = 1;
+ public static final int MESSAGE_DIALOG_DISMISSED = 2; // for either cancel or enable pressed
+
+ public static OrbotStartDialogFragment newInstance(Messenger messenger, int title, int message, int middleButton) {
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_MESSENGER, messenger);
+ args.putInt(ARG_TITLE, title);
+ args.putInt(ARG_MESSAGE, message);
+ args.putInt(ARG_MIDDLE_BUTTON, middleButton);
+
+ OrbotStartDialogFragment fragment = new OrbotStartDialogFragment();
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+
+ final Messenger messenger = getArguments().getParcelable(ARG_MESSENGER);
+ int title = getArguments().getInt(ARG_TITLE);
+ final int message = getArguments().getInt(ARG_MESSAGE);
+ int middleButton = getArguments().getInt(ARG_MIDDLE_BUTTON);
+ final Activity activity = getActivity();
+
+ ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity);
+
+ CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme);
+ builder.setTitle(title).setMessage(message);
+
+ builder.setNegativeButton(R.string.orbot_start_dialog_cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Message msg = Message.obtain();
+ msg.what = MESSAGE_DIALOG_DISMISSED;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
+ } catch (NullPointerException e) {
+ Log.w(Constants.TAG, "Messenger is null!", e);
+ }
+
+ }
+ });
+
+ builder.setPositiveButton(R.string.orbot_start_dialog_start, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ getActivity().startActivityForResult(OrbotHelper.getOrbotStartIntent(), 1);
+
+ Message msg = Message.obtain();
+ msg.what = MESSAGE_DIALOG_DISMISSED;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
+ } catch (NullPointerException e) {
+ Log.w(Constants.TAG, "Messenger is null!", e);
+ }
+ }
+ });
+
+ builder.setNeutralButton(middleButton, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Message msg = new Message();
+ msg.what = MESSAGE_MIDDLE_BUTTON;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
+ } catch (NullPointerException e) {
+ Log.w(Constants.TAG, "Messenger is null!", e);
+ }
+ }
+ });
+
+ return builder.show();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java
new file mode 100644
index 000000000..3f8bce28b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012-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.dialog;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.os.Messenger;
+import android.app.DialogFragment;
+
+import org.sufficientlysecure.keychain.ui.util.InstallDialogFragmentHelper;
+
+public class PreferenceInstallDialogFragment extends DialogFragment {
+
+ public static final int MESSAGE_MIDDLE_CLICKED = 1;
+ public static final int MESSAGE_DIALOG_DISMISSED = 2;
+
+ /**
+ * Creates a dialog which prompts the user to install an application. Consists of two default buttons ("Install"
+ * and "Cancel") and an optional third button. Callbacks are provided only for the middle button, if set.
+ *
+ * @param messenger required only for callback from middle button if it has been set
+ * @param title
+ * @param message content of dialog
+ * @param packageToInstall package name of application to install
+ * @param middleButton if not null, adds a third button to the app with a call back
+ * @return The dialog to display
+ */
+ public static PreferenceInstallDialogFragment newInstance(Messenger messenger, int title, int message,
+ String packageToInstall, int middleButton, boolean
+ useMiddleButton) {
+ PreferenceInstallDialogFragment frag = new PreferenceInstallDialogFragment();
+ Bundle args = new Bundle();
+
+ InstallDialogFragmentHelper.wrapIntoArgs(messenger, title, message, packageToInstall, middleButton,
+ useMiddleButton, args);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * To create a DialogFragment with only two buttons
+ *
+ * @param title
+ * @param message
+ * @param packageToInstall
+ * @return
+ */
+ public static PreferenceInstallDialogFragment newInstance(int title, int message,
+ String packageToInstall) {
+ return newInstance(null, title, message, packageToInstall, -1, false);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return InstallDialogFragmentHelper.getInstallDialogFromArgs(getArguments(), getActivity(),
+ MESSAGE_MIDDLE_CLICKED, MESSAGE_DIALOG_DISMISSED);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java
index 52a90b323..764291dd0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java
@@ -35,6 +35,7 @@ import android.widget.Button;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.KeychainService;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
/**
* meant to be used
@@ -98,10 +99,7 @@ public class ProgressDialogFragment extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
- // if the progress dialog is displayed from the application class, design is missing
- // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay
- ContextThemeWrapper context = new ContextThemeWrapper(activity,
- R.style.Theme_AppCompat_Light);
+ ContextThemeWrapper context = ThemeChanger.getDialogThemeWrapper(activity);
ProgressDialog dialog = new ProgressDialog(context);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java
index 4eb253825..a990682f6 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java
@@ -18,7 +18,7 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Activity;
-import android.app.AlertDialog;
+import android.support.v7.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java
new file mode 100644
index 000000000..b2b71b364
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012-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.dialog;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.os.Messenger;
+import android.support.v4.app.DialogFragment;
+
+import org.sufficientlysecure.keychain.ui.util.InstallDialogFragmentHelper;
+
+public class SupportInstallDialogFragment extends DialogFragment {
+
+ public static final int MESSAGE_MIDDLE_CLICKED = 1;
+ public static final int MESSAGE_DIALOG_DISMISSED = 2;
+
+ /**
+ * Creates a dialog which prompts the user to install an application. Consists of two default buttons ("Install"
+ * and "Cancel") and an optional third button. Callbacks are provided only for the middle button, if set.
+ *
+ * @param messenger required only for callback from middle button if it has been set
+ * @param title
+ * @param message content of dialog
+ * @param packageToInstall package name of application to install
+ * @param middleButton if not null, adds a third button to the app with a call back
+ * @return The dialog to display
+ */
+ public static SupportInstallDialogFragment newInstance(Messenger messenger, int title, int message,
+ String packageToInstall, int middleButton, boolean
+ useMiddleButton) {
+ SupportInstallDialogFragment frag = new SupportInstallDialogFragment();
+ Bundle args = new Bundle();
+
+ InstallDialogFragmentHelper.wrapIntoArgs(messenger, title, message, packageToInstall, middleButton,
+ useMiddleButton, args);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * To create a DialogFragment with only two buttons
+ *
+ * @param title
+ * @param message
+ * @param packageToInstall
+ * @return
+ */
+ public static SupportInstallDialogFragment newInstance(int title, int message,
+ String packageToInstall) {
+ return newInstance(null, title, message, packageToInstall, -1, false);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+
+ return InstallDialogFragmentHelper.getInstallDialogFromArgs(getArguments(), getActivity(),
+ MESSAGE_MIDDLE_CLICKED, MESSAGE_DIALOG_DISMISSED);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java
index eb5c3df45..902a7ec56 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java
@@ -18,9 +18,11 @@
package org.sufficientlysecure.keychain.ui.util;
import android.content.Context;
+import android.content.res.Resources.Theme;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.StrikethroughSpan;
+import android.util.TypedValue;
public class FormattingUtils {
@@ -32,4 +34,10 @@ public class FormattingUtils {
return (int) ((px / context.getResources().getDisplayMetrics().density) + 0.5f);
}
+ public static int getColorFromAttr(Context context, int attr) {
+ TypedValue typedValue = new TypedValue();
+ Theme theme = context.getTheme();
+ theme.resolveAttribute(attr, typedValue, true);
+ return typedValue.data;
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java
index 69338aa3e..ac34d5526 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java
@@ -22,6 +22,7 @@ import android.text.Spannable;
import android.text.style.ForegroundColorSpan;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -44,9 +45,12 @@ public class Highlighter {
Pattern pattern = Pattern.compile("(?i)(" + mQuery.trim().replaceAll("\\s+", "|") + ")");
Matcher matcher = pattern.matcher(text);
+
+ int colorEmphasis = FormattingUtils.getColorFromAttr(mContext, R.attr.colorEmphasis);
+
while (matcher.find()) {
highlight.setSpan(
- new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)),
+ new ForegroundColorSpan(colorEmphasis),
matcher.start(),
matcher.end(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java
new file mode 100644
index 000000000..b2213ed10
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2012-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.util;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.v7.app.AlertDialog;
+import android.view.ContextThemeWrapper;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class InstallDialogFragmentHelper {
+ private static final String ARG_MESSENGER = "messenger";
+ private static final String ARG_TITLE = "title";
+ private static final String ARG_MESSAGE = "message";
+ private static final String ARG_MIDDLE_BUTTON = "middleButton";
+ private static final String ARG_INSTALL_PATH = "installPath";
+ private static final String ARG_USE_MIDDLE_BUTTON = "useMiddleButton";
+
+ private static final String PLAY_STORE_PATH = "market://search?q=pname:";
+
+ public static void wrapIntoArgs(Messenger messenger, int title, int message, String packageToInstall,
+ int middleButton, boolean useMiddleButton, Bundle args) {
+ args.putParcelable(ARG_MESSENGER, messenger);
+
+ args.putInt(ARG_TITLE, title);
+ args.putInt(ARG_MESSAGE, message);
+ args.putInt(ARG_MIDDLE_BUTTON, middleButton);
+ args.putString(ARG_INSTALL_PATH, PLAY_STORE_PATH + packageToInstall);
+ args.putBoolean(ARG_USE_MIDDLE_BUTTON, useMiddleButton);
+ }
+
+ public static AlertDialog getInstallDialogFromArgs(Bundle args, final Activity activity,
+ final int messengerMiddleButtonClicked,
+ final int messengerDialogDimissed) {
+ final Messenger messenger = args.getParcelable(ARG_MESSENGER);
+
+ final int title = args.getInt(ARG_TITLE);
+ final int message = args.getInt(ARG_MESSAGE);
+ final int middleButton = args.getInt(ARG_MIDDLE_BUTTON);
+ final String installPath = args.getString(ARG_INSTALL_PATH);
+ final boolean useMiddleButton = args.getBoolean(ARG_USE_MIDDLE_BUTTON);
+
+ ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity);
+ CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme);
+
+ builder.setTitle(title).setMessage(message);
+
+ builder.setNegativeButton(R.string.orbot_install_dialog_cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Message msg = Message.obtain();
+ msg.what = messengerDialogDimissed;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
+ } catch (NullPointerException e) {
+ Log.w(Constants.TAG, "Messenger is null!", e);
+ }
+ }
+ });
+
+ builder.setPositiveButton(R.string.orbot_install_dialog_install,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Uri uri = Uri.parse(installPath);
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ activity.startActivity(intent);
+
+ Message msg = Message.obtain();
+ msg.what = messengerDialogDimissed;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
+ } catch (NullPointerException e) {
+ Log.w(Constants.TAG, "Messenger is null!", e);
+ }
+ }
+ }
+ );
+
+ if (useMiddleButton) {
+ builder.setNeutralButton(middleButton,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Message msg = Message.obtain();
+ msg.what = messengerMiddleButtonClicked;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
+ } catch (NullPointerException e) {
+ Log.w(Constants.TAG, "Messenger is null!", e);
+ }
+ }
+ }
+ );
+ }
+
+ return builder.show();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java
index a3cd63d13..224e0085b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java
@@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
+import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
import java.nio.ByteBuffer;
@@ -449,11 +450,11 @@ public class KeyFormattingUtils {
if (signatureResult != null && signatureResult.isSignatureOnly()) {
encIcon = R.drawable.status_lock_open_24dp;
encText = R.string.decrypt_result_not_encrypted;
- encColor = R.color.android_red_light;
+ encColor = R.color.key_flag_red;
} else {
encIcon = R.drawable.status_lock_closed_24dp;
encText = R.string.decrypt_result_encrypted;
- encColor = R.color.android_green_light;
+ encColor = R.color.key_flag_green;
}
int encColorRes = context.getResources().getColor(encColor);
@@ -470,7 +471,7 @@ public class KeyFormattingUtils {
sigText = R.string.decrypt_result_no_signature;
sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
- sigColor = R.color.bg_gray;
+ sigColor = R.color.key_flag_gray;
// won't be used, but makes compiler happy
sigActionText = 0;
@@ -481,7 +482,7 @@ public class KeyFormattingUtils {
case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: {
sigText = R.string.decrypt_result_signature_certified;
sigIcon = R.drawable.status_signature_verified_cutout_24dp;
- sigColor = R.color.android_green_light;
+ sigColor = R.color.key_flag_green;
sigActionText = R.string.decrypt_result_action_show;
sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
@@ -491,7 +492,7 @@ public class KeyFormattingUtils {
case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: {
sigText = R.string.decrypt_result_signature_uncertified;
sigIcon = R.drawable.status_signature_unverified_cutout_24dp;
- sigColor = R.color.android_orange_light;
+ sigColor = R.color.key_flag_orange;
sigActionText = R.string.decrypt_result_action_show;
sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
@@ -501,7 +502,7 @@ public class KeyFormattingUtils {
case OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED: {
sigText = R.string.decrypt_result_signature_revoked_key;
sigIcon = R.drawable.status_signature_revoked_cutout_24dp;
- sigColor = R.color.android_red_light;
+ sigColor = R.color.key_flag_red;
sigActionText = R.string.decrypt_result_action_show;
sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
@@ -511,7 +512,7 @@ public class KeyFormattingUtils {
case OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED: {
sigText = R.string.decrypt_result_signature_expired_key;
sigIcon = R.drawable.status_signature_expired_cutout_24dp;
- sigColor = R.color.android_red_light;
+ sigColor = R.color.key_flag_red;
sigActionText = R.string.decrypt_result_action_show;
sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
@@ -521,7 +522,7 @@ public class KeyFormattingUtils {
case OpenPgpSignatureResult.SIGNATURE_KEY_MISSING: {
sigText = R.string.decrypt_result_signature_missing_key;
sigIcon = R.drawable.status_signature_unknown_cutout_24dp;
- sigColor = R.color.android_red_light;
+ sigColor = R.color.key_flag_red;
sigActionText = R.string.decrypt_result_action_Lookup;
sigActionIcon = R.drawable.ic_file_download_grey_24dp;
@@ -532,7 +533,7 @@ public class KeyFormattingUtils {
case OpenPgpSignatureResult.SIGNATURE_ERROR: {
sigText = R.string.decrypt_result_invalid_signature;
sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
- sigColor = R.color.android_red_light;
+ sigColor = R.color.key_flag_red;
sigActionText = R.string.decrypt_result_action_show;
sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
@@ -595,7 +596,7 @@ public class KeyFormattingUtils {
context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_24dp));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_green_light;
+ color = R.color.key_flag_green;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -608,7 +609,7 @@ public class KeyFormattingUtils {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_lock_closed_24dp));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_green_light;
+ color = R.color.key_flag_green;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -627,7 +628,7 @@ public class KeyFormattingUtils {
context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_24dp));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_orange_light;
+ color = R.color.key_flag_orange;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -640,7 +641,7 @@ public class KeyFormattingUtils {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24dp));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_red_light;
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -659,7 +660,7 @@ public class KeyFormattingUtils {
context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_24dp));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_red_light;
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -677,7 +678,7 @@ public class KeyFormattingUtils {
context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_24dp));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_red_light;
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -690,7 +691,7 @@ public class KeyFormattingUtils {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_lock_open_24dp));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_red_light;
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -703,7 +704,7 @@ public class KeyFormattingUtils {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24dp));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_red_light;
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -716,7 +717,7 @@ public class KeyFormattingUtils {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.android_red_light;
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -730,7 +731,7 @@ public class KeyFormattingUtils {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
- color = R.color.bg_gray;
+ color = R.color.key_flag_gray;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java
new file mode 100644
index 000000000..f53e43528
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import android.content.Context;
+import android.view.ContextThemeWrapper;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Preferences;
+
+public class ThemeChanger {
+ private Context mContext;
+ private Preferences mPreferences;
+ private String mCurrentTheme = null;
+
+ static public ContextThemeWrapper getDialogThemeWrapper(Context context) {
+ Preferences preferences = Preferences.getPreferences(context);
+
+ // if the dialog is displayed from the application class, design is missing.
+ // hack to get holo design (which is not automatically applied due to activity's
+ // Theme.NoDisplay)
+ if (Constants.Pref.Theme.DARK.equals(preferences.getTheme())) {
+ return new ContextThemeWrapper(context, R.style.Theme_AppCompat_Dialog);
+ } else {
+ return new ContextThemeWrapper(context, R.style.Theme_AppCompat_Light_Dialog);
+ }
+ }
+
+ public ThemeChanger(Context context) {
+ mContext = context;
+ mPreferences = Preferences.getPreferences(mContext);
+ }
+
+ /**
+ * Apply the theme set in preferences if it isn't equal to mCurrentTheme
+ * anymore or mCurrentTheme hasn't been set yet.
+ * If a new theme is applied in this method, then return true, so
+ * the caller can re-create the activity, if need be.
+ */
+ public boolean changeTheme() {
+ String newTheme = mPreferences.getTheme();
+ if (mCurrentTheme != null && mCurrentTheme.equals(newTheme)) {
+ return false;
+ }
+
+ int themeId = R.style.LightTheme;
+ if (Constants.Pref.Theme.DARK.equals(newTheme)) {
+ themeId = R.style.DarkTheme;
+ }
+
+ ContextThemeWrapper w = new ContextThemeWrapper(mContext, themeId);
+ mContext.getTheme().setTo(w.getTheme());
+ mCurrentTheme = newTheme;
+
+ return true;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java
new file mode 100644
index 000000000..95199bcd5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.ui.util.recyclerview;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+public class DividerItemDecoration extends RecyclerView.ItemDecoration {
+
+ private static final int[] ATTRS = new int[]{
+ android.R.attr.listDivider
+ };
+
+ public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
+
+ public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
+
+ private Drawable mDivider;
+
+ private int mOrientation;
+
+ public DividerItemDecoration(Context context, int orientation) {
+ final TypedArray a = context.obtainStyledAttributes(ATTRS);
+ mDivider = a.getDrawable(0);
+ a.recycle();
+ setOrientation(orientation);
+ }
+
+ public void setOrientation(int orientation) {
+ if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
+ throw new IllegalArgumentException("invalid orientation");
+ }
+ mOrientation = orientation;
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ if (mOrientation == VERTICAL_LIST) {
+ drawVertical(c, parent);
+ } else {
+ drawHorizontal(c, parent);
+ }
+ }
+
+ public void drawVertical(Canvas c, RecyclerView parent) {
+ final int left = parent.getPaddingLeft();
+ final int right = parent.getWidth() - parent.getPaddingRight();
+
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ final int top = child.getBottom() + params.bottomMargin;
+ final int bottom = top + mDivider.getIntrinsicHeight();
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+
+ public void drawHorizontal(Canvas c, RecyclerView parent) {
+ final int top = parent.getPaddingTop();
+ final int bottom = parent.getHeight() - parent.getPaddingBottom();
+
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ final int left = child.getRight() + params.rightMargin;
+ final int right = left + mDivider.getIntrinsicHeight();
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ if (mOrientation == VERTICAL_LIST) {
+ outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
+ } else {
+ outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
+ }
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java
new file mode 100644
index 000000000..c691182bf
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 Paul Burke
+ *
+ * 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.ui.util.recyclerview;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+
+/**
+ * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}.
+ */
+public interface ItemTouchHelperAdapter {
+
+ /**
+ * Called when an item has been dragged far enough to trigger a move. This is called every time
+ * an item is shifted, and <strong>not</strong> at the end of a "drop" event.<br/>
+ * <br/>
+ * Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after
+ * adjusting the underlying data to reflect this move.
+ *
+ * @param fromPosition The start position of the moved item.
+ * @param toPosition Then resolved position of the moved item.
+ * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
+ * @see RecyclerView.ViewHolder#getAdapterPosition()
+ */
+ void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target,
+ int fromPosition, int toPosition);
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java
new file mode 100644
index 000000000..0fd24581d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 Paul Burke
+ *
+ * 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.ui.util.recyclerview;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+
+/**
+ * An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and
+ * swipe-to-dismiss. Drag events are automatically started by an item long-press.<br/>
+ * </br/>
+ * Expects the <code>RecyclerView.Adapter</code> to listen for {@link
+ * ItemTouchHelperAdapter} callbacks and the <code>RecyclerView.ViewHolder</code> to implement
+ * {@link ItemTouchHelperViewHolder}.
+ */
+public class ItemTouchHelperDragCallback extends ItemTouchHelper.Callback {
+
+ private final ItemTouchHelperAdapter mAdapter;
+
+ public ItemTouchHelperDragCallback(ItemTouchHelperAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ @Override
+ public boolean isLongPressDragEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isItemViewSwipeEnabled() {
+ return false;
+ }
+
+ @Override
+ public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ // Enable drag and swipe in both directions
+ final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
+ final int swipeFlags = 0;
+ return makeMovementFlags(dragFlags, swipeFlags);
+ }
+
+ @Override
+ public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source,
+ RecyclerView.ViewHolder target) {
+ if (source.getItemViewType() != target.getItemViewType()) {
+ return false;
+ }
+
+ // Notify the adapter of the move
+ mAdapter.onItemMove(source, target, source.getAdapterPosition(), target.getAdapterPosition());
+ return true;
+ }
+
+ @Override
+ public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
+ // we don't support swipe
+ }
+
+ @Override
+ public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
+ if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
+ // Let the view holder know that this item is being moved or dragged
+ ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
+ itemViewHolder.onItemSelected();
+ }
+
+ super.onSelectedChanged(viewHolder, actionState);
+ }
+
+ @Override
+ public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ super.clearView(recyclerView, viewHolder);
+
+ // Tell the view holder it's time to restore the idle state
+ ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
+ itemViewHolder.onItemClear();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java
new file mode 100644
index 000000000..97e70d71e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 Paul Burke
+ *
+ * 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.ui.util.recyclerview;
+
+import android.support.v7.widget.helper.ItemTouchHelper;
+
+/**
+ * Interface to notify an item ViewHolder of relevant callbacks from {@link
+ * android.support.v7.widget.helper.ItemTouchHelper.Callback}.
+ */
+public interface ItemTouchHelperViewHolder {
+
+ /**
+ * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped.
+ * Implementations should update the item view to indicate it's active state.
+ */
+ void onItemSelected();
+
+
+ /**
+ * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item
+ * state should be cleared.
+ */
+ void onItemClear();
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java
new file mode 100644
index 000000000..7efcbb30c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 Jacob Tabak
+ *
+ * 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.util.recyclerview;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * based on http://stackoverflow.com/a/26196831/3000919
+ */
+public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
+ private OnItemClickListener mListener;
+ private boolean mIgnoreTouch = false;
+
+ public interface OnItemClickListener {
+ void onItemClick(View view, int position);
+ }
+
+ GestureDetector mGestureDetector;
+
+ public RecyclerItemClickListener(Context context, OnItemClickListener listener) {
+ mListener = listener;
+ mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
+ if (mIgnoreTouch) {
+ return false;
+ }
+ View childView = view.findChildViewUnder(e.getX(), e.getY());
+ if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
+ mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) {
+ // TODO: should we move mListener.onItemClick here
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ mIgnoreTouch = disallowIntercept;
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java
index 0fed46544..6cd33aada 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java
@@ -109,14 +109,18 @@ public class CertifyKeySpinner extends KeySpinner {
@Override
boolean isItemEnabled(Cursor cursor) {
+ // "none" entry is always enabled!
+ if (cursor.getPosition() == 0) {
+ return true;
+ }
+
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
return false;
}
if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
return false;
}
- // don't invalidate the "None" entry, which is also null!
- if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) {
+ if (cursor.isNull(mIndexHasCertify)) {
return false;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java
deleted file mode 100644
index ec91b9fe4..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.widget;
-
-public interface Editor {
- public interface EditorListener {
- public void onDeleted(Editor editor, boolean wasNewItem);
- public void onEdited();
- }
-
- public void setEditorListener(EditorListener listener);
- public boolean needsSaving();
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java
index e55f6b1ad..494ccb6d3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java
@@ -75,10 +75,10 @@ public class EmailEditText extends AppCompatAutoCompleteTextView {
Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
if (emailMatcher.matches()) {
EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0,
- R.drawable.uid_mail_ok, 0);
+ R.drawable.ic_stat_retyped_ok, 0);
} else {
EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0,
- R.drawable.uid_mail_bad, 0);
+ R.drawable.ic_stat_retyped_bad, 0);
}
} else {
// remove drawable if email is empty
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java
deleted file mode 100644
index 3fd01958a..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.sufficientlysecure.keychain.R;
-
-public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener {
- private EditorListener mEditorListener = null;
-
- ImageButton mDeleteButton;
- TextView mServer;
-
- public KeyServerEditor(Context context) {
- super(context);
- }
-
- public KeyServerEditor(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- setDrawingCacheEnabled(true);
- setAlwaysDrawnWithCacheEnabled(true);
-
- mServer = (TextView) findViewById(R.id.server);
-
- mDeleteButton = (ImageButton) findViewById(R.id.delete);
- mDeleteButton.setOnClickListener(this);
-
- super.onFinishInflate();
- }
-
- public void setValue(String value) {
- mServer.setText(value);
- }
-
- public String getValue() {
- return mServer.getText().toString().trim();
- }
-
- public void onClick(View v) {
- final ViewGroup parent = (ViewGroup) getParent();
- if (v == mDeleteButton) {
- parent.removeView(this);
- if (mEditorListener != null) {
- mEditorListener.onDeleted(this, false);
- }
- }
- }
-
- @Override
- public boolean needsSaving() {
- return false;
- }
-
- public void setEditorListener(EditorListener listener) {
- mEditorListener = listener;
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java
index 5050c01af..1884daf12 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java
@@ -84,7 +84,8 @@ public abstract class KeySpinner extends AppCompatSpinner implements
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (mListener != null) {
- mListener.onKeyChanged(id);
+ long keyId = getSelectedKeyId(getItemAtPosition(position));
+ mListener.onKeyChanged(keyId);
}
}
@@ -137,6 +138,10 @@ public abstract class KeySpinner extends AppCompatSpinner implements
public long getSelectedKeyId() {
Object item = getSelectedItem();
+ return getSelectedKeyId(item);
+ }
+
+ public long getSelectedKeyId(Object item) {
if (item instanceof KeyItem) {
return ((KeyItem) item).mKeyId;
}
@@ -186,7 +191,7 @@ public abstract class KeySpinner extends AppCompatSpinner implements
}
@Override
- public Object getItem(int position) {
+ public KeyItem getItem(int position) {
if (position == 0) {
return null;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java
index 0ec145657..a7ead8039 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java
@@ -31,7 +31,9 @@ import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
+import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
/**
* Created by Matt Allen
@@ -97,9 +99,9 @@ public class PasswordStrengthView extends View {
public PasswordStrengthView(Context context, AttributeSet attrs) {
super(context, attrs);
- int COLOR_FAIL = getResources().getColor(R.color.android_red_light);
- int COLOR_WEAK = getResources().getColor(R.color.android_orange_light);
- int COLOR_STRONG = getResources().getColor(R.color.android_green_light);
+ int COLOR_FAIL = getResources().getColor(R.color.password_strength_low);
+ int COLOR_WEAK = getResources().getColor(R.color.password_strength_medium);
+ int COLOR_STRONG = getResources().getColor(R.color.password_strength_high);
TypedArray style = context.getTheme().obtainStyledAttributes(
attrs,
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java
index c59ad7a12..8fb9e38aa 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java
@@ -72,13 +72,18 @@ public class SignKeySpinner extends KeySpinner {
@Override
boolean isItemEnabled(Cursor cursor) {
+ // "none" entry is always enabled!
+ if (cursor.getPosition() == 0) {
+ return true;
+ }
+
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
return false;
}
if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
return false;
}
- if (cursor.getInt(mIndexHasSign) == 0) {
+ if (cursor.isNull(mIndexHasSign)) {
return false;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
index b814f72b2..d7491ab26 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
@@ -27,6 +27,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
+import java.net.Proxy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -34,6 +35,7 @@ import java.util.Locale;
import java.util.Set;
public class EmailKeyHelper {
+ // TODO: Make this not require a proxy in it's constructor, redesign when it is to be used
// to import keys, simply use CryptoOperationHelper with this callback
public abstract class ImportContactKeysCallback
implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
@@ -41,14 +43,15 @@ public class EmailKeyHelper {
private ArrayList<ParcelableKeyRing> mKeyList;
private String mKeyserver;
- public ImportContactKeysCallback(Context context, String keyserver) {
- this(context, ContactHelper.getContactMails(context), keyserver);
+ public ImportContactKeysCallback(Context context, String keyserver, Proxy proxy) {
+ this(context, ContactHelper.getContactMails(context), keyserver, proxy);
}
- public ImportContactKeysCallback(Context context, List<String> mails, String keyserver) {
+ public ImportContactKeysCallback(Context context, List<String> mails, String keyserver,
+ Proxy proxy) {
Set<ImportKeysListEntry> entries = new HashSet<>();
for (String mail : mails) {
- entries.addAll(getEmailKeys(context, mail));
+ entries.addAll(getEmailKeys(context, mail, proxy));
}
// Put them in a list and import
@@ -65,7 +68,7 @@ public class EmailKeyHelper {
}
}
- public static Set<ImportKeysListEntry> getEmailKeys(Context context, String mail) {
+ public static Set<ImportKeysListEntry> getEmailKeys(Context context, String mail, Proxy proxy) {
Set<ImportKeysListEntry> keys = new HashSet<>();
// Try _hkp._tcp SRV record first
@@ -73,7 +76,7 @@ public class EmailKeyHelper {
if (mailparts.length == 2) {
HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1]);
if (hkp != null) {
- keys.addAll(getEmailKeys(mail, hkp));
+ keys.addAll(getEmailKeys(mail, hkp, proxy));
}
}
@@ -82,16 +85,17 @@ public class EmailKeyHelper {
String server = Preferences.getPreferences(context).getPreferredKeyserver();
if (server != null) {
HkpKeyserver hkp = new HkpKeyserver(server);
- keys.addAll(getEmailKeys(mail, hkp));
+ keys.addAll(getEmailKeys(mail, hkp, proxy));
}
}
return keys;
}
- public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer) {
+ public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer,
+ Proxy proxy) {
Set<ImportKeysListEntry> keys = new HashSet<>();
try {
- for (ImportKeysListEntry key : keyServer.search(mail)) {
+ for (ImportKeysListEntry key : keyServer.search(mail, proxy)) {
if (key.isRevoked() || key.isExpired()) continue;
for (String userId : key.getUserIds()) {
if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
index 9567fc9c0..5f2329170 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
@@ -17,6 +17,9 @@
package org.sufficientlysecure.keychain.util;
+
+import java.io.File;
+
import android.support.v4.app.FragmentActivity;
import org.sufficientlysecure.keychain.Constants;
@@ -25,15 +28,12 @@ import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
-import java.io.File;
-
public class ExportHelper
implements CryptoOperationHelper.Callback <ExportKeyringParcel, ExportResult> {
protected File mExportFile;
FragmentActivity mActivity;
- private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mExportOpHelper;
private boolean mExportSecret;
private long[] mMasterKeyIds;
@@ -42,15 +42,13 @@ public class ExportHelper
this.mActivity = activity;
}
- /**
- * Show dialog where to export keys
- */
- public void showExportKeysDialog(final long[] masterKeyIds, final File exportFile,
- final boolean showSecretCheckbox) {
+ /** Show dialog where to export keys */
+ public void showExportKeysDialog(final Long masterKeyId, final File exportFile,
+ final boolean exportSecret) {
mExportFile = exportFile;
- String title = null;
- if (masterKeyIds == null) {
+ String title;
+ if (masterKeyId == null) {
// export all keys
title = mActivity.getString(R.string.title_export_keys);
} else {
@@ -58,17 +56,24 @@ public class ExportHelper
title = mActivity.getString(R.string.title_export_key);
}
- String message = mActivity.getString(R.string.specify_file_to_export_to);
- String checkMsg = showSecretCheckbox ?
- mActivity.getString(R.string.also_export_secret_keys) : null;
+ String message;
+ if (exportSecret) {
+ message = mActivity.getString(masterKeyId == null
+ ? R.string.specify_backup_dest_secret
+ : R.string.specify_backup_dest_secret_single);
+ } else {
+ message = mActivity.getString(masterKeyId == null
+ ? R.string.specify_backup_dest
+ : R.string.specify_backup_dest_single);
+ }
FileHelper.saveFile(new FileHelper.FileDialogCallback() {
@Override
public void onFileSelected(File file, boolean checked) {
mExportFile = file;
- exportKeys(masterKeyIds, checked);
+ exportKeys(masterKeyId == null ? null : new long[] { masterKeyId }, exportSecret);
}
- }, mActivity.getSupportFragmentManager() ,title, message, exportFile, checkMsg);
+ }, mActivity.getSupportFragmentManager(), title, message, exportFile, null);
}
// TODO: If ExportHelper requires pending data (see CryptoOPerationHelper), activities using
@@ -82,8 +87,9 @@ public class ExportHelper
mExportSecret = exportSecret;
mMasterKeyIds = masterKeyIds; // if masterKeyIds is null it means export all
- mExportOpHelper = new CryptoOperationHelper(mActivity, this, R.string.progress_exporting);
- mExportOpHelper.cryptoOperation();
+ CryptoOperationHelper<ExportKeyringParcel, ExportResult> exportOpHelper =
+ new CryptoOperationHelper<>(1, mActivity, this, R.string.progress_exporting);
+ exportOpHelper.cryptoOperation();
}
@Override
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
new file mode 100644
index 000000000..b00b03ec7
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
@@ -0,0 +1,88 @@
+/*
+ * 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 android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+
+/**
+ * used to simply transport java.net.Proxy objects created using InetSockets between services/activities
+ */
+public class ParcelableProxy implements Parcelable {
+ private String mProxyHost;
+ private int mProxyPort;
+ private Proxy.Type mProxyType;
+
+ public ParcelableProxy(String hostName, int port, Proxy.Type type) {
+ mProxyHost = hostName;
+
+ if (hostName == null) {
+ return; // represents a null proxy
+ }
+
+ mProxyPort = port;
+
+ mProxyType = type;
+ }
+
+ public static ParcelableProxy getForNoProxy() {
+ return new ParcelableProxy(null, -1, null);
+ }
+
+ public Proxy getProxy() {
+ if (mProxyHost == null) {
+ return null;
+ }
+
+ return new Proxy(mProxyType, new InetSocketAddress(mProxyHost, mProxyPort));
+ }
+
+ protected ParcelableProxy(Parcel in) {
+ mProxyHost = in.readString();
+ mProxyPort = in.readInt();
+ mProxyType = (Proxy.Type) in.readSerializable();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mProxyHost);
+ dest.writeInt(mProxyPort);
+ dest.writeSerializable(mProxyType);
+ }
+
+ @SuppressWarnings("unused")
+ public static final Parcelable.Creator<ParcelableProxy> CREATOR = new Parcelable.Creator<ParcelableProxy>() {
+ @Override
+ public ParcelableProxy createFromParcel(Parcel in) {
+ return new ParcelableProxy(in);
+ }
+
+ @Override
+ public ParcelableProxy[] newArray(int size) {
+ return new ParcelableProxy[size];
+ }
+ };
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java
index 06efdde4d..fe42c7a2c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java
@@ -117,6 +117,13 @@ public class Passphrase implements Parcelable {
}
}
+ /**
+ * Creates a new String from the char[]. This is considered unsafe!
+ */
+ public String toStringUnsafe() {
+ return new String(mPassphrase);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -127,11 +134,7 @@ public class Passphrase implements Parcelable {
}
Passphrase that = (Passphrase) o;
- if (!Arrays.equals(mPassphrase, that.mPassphrase)) {
- return false;
- }
-
- return true;
+ return Arrays.equals(mPassphrase, that.mPassphrase);
}
@Override
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
index a5b0088c0..0596b0079 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
@@ -21,9 +21,13 @@ package org.sufficientlysecure.keychain.util;
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.preference.PreferenceManager;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.Pref;
+import org.sufficientlysecure.keychain.R;
+import java.net.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ListIterator;
@@ -35,6 +39,10 @@ import java.util.Vector;
public class Preferences {
private static Preferences sPreferences;
private SharedPreferences mSharedPreferences;
+ private Resources mResources;
+
+ private static String PREF_FILE_NAME = "APG.main";
+ private static int PREF_FILE_MODE = Context.MODE_MULTI_PROCESS;
public static synchronized Preferences getPreferences(Context context) {
return getPreferences(context, false);
@@ -51,12 +59,18 @@ public class Preferences {
}
private Preferences(Context context) {
+ mResources = context.getResources();
updateSharedPreferences(context);
}
+ public static void setPreferenceManagerFileAndMode(PreferenceManager manager) {
+ manager.setSharedPreferencesName(PREF_FILE_NAME);
+ manager.setSharedPreferencesMode(PREF_FILE_MODE);
+ }
+
public void updateSharedPreferences(Context context) {
// multi-process safe preferences
- mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_MULTI_PROCESS);
+ mSharedPreferences = context.getSharedPreferences(PREF_FILE_NAME, PREF_FILE_MODE);
}
public String getLanguage() {
@@ -138,6 +152,9 @@ public class Preferences {
public String[] getKeyServers() {
String rawData = mSharedPreferences.getString(Constants.Pref.KEY_SERVERS,
Constants.Defaults.KEY_SERVERS);
+ if (rawData.equals("")) {
+ return new String[0];
+ }
Vector<String> servers = new Vector<>();
String chunks[] = rawData.split(",");
for (String c : chunks) {
@@ -150,7 +167,8 @@ public class Preferences {
}
public String getPreferredKeyserver() {
- return getKeyServers()[0];
+ String[] keyservers = getKeyServers();
+ return keyservers.length == 0 ? null : keyservers[0];
}
public void setKeyServers(String[] value) {
@@ -202,7 +220,15 @@ public class Preferences {
return mSharedPreferences.getBoolean(Pref.TEXT_USE_COMPRESSION, true);
}
+ public String getTheme() {
+ return mSharedPreferences.getString(Pref.THEME, Pref.Theme.LIGHT);
+ }
+ public void setTheme(String value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putString(Constants.Pref.THEME, value);
+ editor.commit();
+ }
public void setUseArmor(boolean useArmor) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
@@ -224,6 +250,89 @@ public class Preferences {
return mSharedPreferences.getBoolean(Pref.ENCRYPT_FILENAMES, true);
}
+ // proxy preference functions start here
+
+ public boolean getUseNormalProxy() {
+ return mSharedPreferences.getBoolean(Constants.Pref.USE_NORMAL_PROXY, false);
+ }
+
+ public boolean getUseTorProxy() {
+ return mSharedPreferences.getBoolean(Constants.Pref.USE_TOR_PROXY, false);
+ }
+
+ public String getProxyHost() {
+ return mSharedPreferences.getString(Constants.Pref.PROXY_HOST, null);
+ }
+
+ /**
+ * we store port as String for easy interfacing with EditTextPreference, but return it as an integer
+ *
+ * @return port number of proxy
+ */
+ public int getProxyPort() {
+ return Integer.parseInt(mSharedPreferences.getString(Pref.PROXY_PORT, "-1"));
+ }
+
+ /**
+ * we store port as String for easy interfacing with EditTextPreference, but return it as an integer
+ *
+ * @param port proxy port
+ */
+ public void setProxyPort(String port) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putString(Pref.PROXY_PORT, port);
+ editor.commit();
+ }
+
+ public Proxy.Type getProxyType() {
+ final String typeHttp = mResources.getString(R.string.pref_proxy_type_value_http);
+ final String typeSocks = mResources.getString(R.string.pref_proxy_type_value_socks);
+
+ String type = mSharedPreferences.getString(Pref.PROXY_TYPE, typeHttp);
+
+ if (type.equals(typeHttp)) return Proxy.Type.HTTP;
+ else if (type.equals(typeSocks)) return Proxy.Type.SOCKS;
+ else { // shouldn't happen
+ Log.e(Constants.TAG, "Invalid Proxy Type in preferences");
+ return null;
+ }
+ }
+
+ public ProxyPrefs getProxyPrefs() {
+ boolean useTor = getUseTorProxy();
+ boolean useNormalProxy = getUseNormalProxy();
+
+ if (useTor) {
+ return new ProxyPrefs(true, false, Constants.Orbot.PROXY_HOST, Constants.Orbot.PROXY_PORT,
+ Constants.Orbot.PROXY_TYPE);
+ } else if (useNormalProxy) {
+ return new ProxyPrefs(useTor, useNormalProxy, getProxyHost(), getProxyPort(), getProxyType());
+ } else {
+ return new ProxyPrefs(false, false, null, -1, null);
+ }
+ }
+
+ public static class ProxyPrefs {
+ public final ParcelableProxy parcelableProxy;
+ public final boolean torEnabled;
+ public final boolean normalPorxyEnabled;
+
+ /**
+ * torEnabled and normalProxyEnabled are not expected to both be true
+ *
+ * @param torEnabled if Tor is to be used
+ * @param normalPorxyEnabled if user-specified proxy is to be used
+ */
+ public ProxyPrefs(boolean torEnabled, boolean normalPorxyEnabled, String hostName, int port, Proxy.Type type) {
+ this.torEnabled = torEnabled;
+ this.normalPorxyEnabled = normalPorxyEnabled;
+ if (!torEnabled && !normalPorxyEnabled) this.parcelableProxy = new ParcelableProxy(null, -1, null);
+ else this.parcelableProxy = new ParcelableProxy(hostName, port, type);
+ }
+ }
+
+ // proxy preference functions ends here
+
public CloudSearchPrefs getCloudSearchPrefs() {
return new CloudSearchPrefs(mSharedPreferences.getBoolean(Pref.SEARCH_KEYSERVER, true),
mSharedPreferences.getBoolean(Pref.SEARCH_KEYBASE, true),
@@ -247,7 +356,7 @@ public class Preferences {
}
}
- public void updatePreferences() {
+ public void upgradePreferences() {
if (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0) !=
Constants.Defaults.PREF_VERSION) {
switch (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0)) {
@@ -281,6 +390,10 @@ public class Preferences {
}
// fall through
case 4: {
+ setTheme(Constants.Pref.Theme.DEFAULT);
+ }
+ // fall through
+ case 5: {
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java
index 4ff14e3bb..d1d1ada2a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java
@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.util;
import android.content.res.AssetManager;
+import com.squareup.okhttp.OkHttpClient;
import org.sufficientlysecure.keychain.Constants;
import java.io.ByteArrayInputStream;
@@ -26,7 +27,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
-import java.net.URLConnection;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -61,7 +61,7 @@ public class TlsHelper {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int reads = is.read();
- while(reads != -1){
+ while (reads != -1) {
baos.write(reads);
reads = is.read();
}
@@ -74,15 +74,56 @@ public class TlsHelper {
}
}
- public static URLConnection openConnection(URL url) throws IOException, TlsHelperException {
+ public static void pinCertificateIfNecessary(OkHttpClient client, URL url) throws TlsHelperException, IOException {
if (url.getProtocol().equals("https")) {
for (String domain : sStaticCA.keySet()) {
if (url.getHost().endsWith(domain)) {
- return openCAConnection(sStaticCA.get(domain), url);
+ pinCertificate(sStaticCA.get(domain), client);
}
}
}
- return url.openConnection();
+ }
+
+ /**
+ * Modifies the client to accept only requests with a given certificate. Applies to all URLs requested by the
+ * client.
+ * Therefore a client that is pinned this way should be used to only make requests to URLs with passed certificate.
+ * TODO: Refactor - More like SSH StrictHostKeyChecking than pinning?
+ *
+ * @param certificate certificate to pin
+ * @param client OkHttpClient to enforce pinning on
+ * @throws TlsHelperException
+ * @throws IOException
+ */
+ private static void pinCertificate(byte[] certificate, OkHttpClient client)
+ throws TlsHelperException, IOException {
+ // We don't use OkHttp's CertificatePinner since it depends on a TrustManager to verify it too. Refer to
+ // note at end of description: http://square.github.io/okhttp/javadoc/com/squareup/okhttp/CertificatePinner.html
+ // Creating our own TrustManager that trusts only our certificate eliminates the need for certificate pinning
+ try {
+ // Load CA
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ Certificate ca = cf.generateCertificate(new ByteArrayInputStream(certificate));
+
+ // Create a KeyStore containing our trusted CAs
+ String keyStoreType = KeyStore.getDefaultType();
+ KeyStore keyStore = KeyStore.getInstance(keyStoreType);
+ keyStore.load(null, null);
+ keyStore.setCertificateEntry("ca", ca);
+
+ // Create a TrustManager that trusts the CAs in our KeyStore
+ String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
+ tmf.init(keyStore);
+
+ // Create an SSLContext that uses our TrustManager
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, tmf.getTrustManagers(), null);
+
+ client.setSslSocketFactory(context.getSocketFactory());
+ } catch (CertificateException | KeyStoreException | KeyManagementException | NoSuchAlgorithmException e) {
+ throw new TlsHelperException(e);
+ }
}
/**
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java
new file mode 100644
index 000000000..9eb92f92f
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java
@@ -0,0 +1,252 @@
+/* This is the license for Orlib, a free software project to
+ provide anonymity on the Internet from a Google Android smartphone.
+
+ For more information about Orlib, see https://guardianproject.info/
+
+ If you got this file as a part of a larger bundle, there may be other
+ license terms that you should be aware of.
+ ===============================================================================
+ Orlib is distributed under this license (aka the 3-clause BSD license)
+
+ Copyright (c) 2009-2010, Nathan Freitas, The Guardian Project
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the names of the copyright owners nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ *****
+ Orlib contains a binary distribution of the JSocks library:
+ http://code.google.com/p/jsocks-mirror/
+ which is licensed under the GNU Lesser General Public License:
+ http://www.gnu.org/licenses/lgpl.html
+
+ *****
+*/
+
+package org.sufficientlysecure.keychain.util.orbot;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.dialog.SupportInstallDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.OrbotStartDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.PreferenceInstallDialogFragment;
+import org.sufficientlysecure.keychain.util.Preferences;
+
+/**
+ * This class is taken from the NetCipher library: https://github.com/guardianproject/NetCipher/
+ */
+public class OrbotHelper {
+
+ public final static String ORBOT_PACKAGE_NAME = "org.torproject.android";
+ public final static String TOR_BIN_PATH = "/data/data/org.torproject.android/app_bin/tor";
+
+ public final static String ACTION_START_TOR = "org.torproject.android.START_TOR";
+
+ public static boolean isOrbotRunning() {
+ int procId = TorServiceUtils.findProcessId(TOR_BIN_PATH);
+
+ return (procId != -1);
+ }
+
+ public static boolean isOrbotInstalled(Context context) {
+ return isAppInstalled(ORBOT_PACKAGE_NAME, context);
+ }
+
+ private static boolean isAppInstalled(String uri, Context context) {
+ PackageManager pm = context.getPackageManager();
+
+ boolean installed;
+ try {
+ pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
+ installed = true;
+ } catch (PackageManager.NameNotFoundException e) {
+ installed = false;
+ }
+ return installed;
+ }
+
+ /**
+ * hack to get around the fact that PreferenceActivity still supports only android.app.DialogFragment
+ *
+ * @return
+ */
+ public static android.app.DialogFragment getPreferenceInstallDialogFragment() {
+ return PreferenceInstallDialogFragment.newInstance(R.string.orbot_install_dialog_title,
+ R.string.orbot_install_dialog_content, ORBOT_PACKAGE_NAME);
+ }
+
+ public static DialogFragment getInstallDialogFragment() {
+ return SupportInstallDialogFragment.newInstance(R.string.orbot_install_dialog_title,
+ R.string.orbot_install_dialog_content, ORBOT_PACKAGE_NAME);
+ }
+
+ public static DialogFragment getInstallDialogFragmentWithThirdButton(Messenger messenger, int middleButton) {
+ return SupportInstallDialogFragment.newInstance(messenger, R.string.orbot_install_dialog_title,
+ R.string.orbot_install_dialog_content, ORBOT_PACKAGE_NAME, middleButton, true);
+ }
+
+ public static DialogFragment getOrbotStartDialogFragment(Messenger messenger, int middleButton) {
+ return OrbotStartDialogFragment.newInstance(messenger, R.string.orbot_start_dialog_title, R.string
+ .orbot_start_dialog_content,
+ middleButton);
+ }
+
+ public static Intent getOrbotStartIntent() {
+ Intent intent = new Intent(ACTION_START_TOR);
+ intent.setPackage(ORBOT_PACKAGE_NAME);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ /**
+ * checks preferences to see if Orbot is required, and if yes, if it is installed and running
+ *
+ * @param context used to retrieve preferences
+ * @return false if Tor is selected proxy and Orbot is not installed or running, true
+ * otherwise
+ */
+ public static boolean isOrbotInRequiredState(Context context) {
+ Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(context).getProxyPrefs();
+ if (!proxyPrefs.torEnabled) {
+ return true;
+ } else if (!OrbotHelper.isOrbotInstalled(context) || !OrbotHelper.isOrbotRunning()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * checks if Tor is enabled and if it is, that Orbot is installed and runnign. Generates appropriate dialogs.
+ *
+ * @param middleButton resourceId of string to display as the middle button of install and enable dialogs
+ * @param middleButtonRunnable runnable to be executed if the user clicks on the middle button
+ * @param proxyPrefs
+ * @param fragmentActivity
+ * @return true if Tor is not enabled or Tor is enabled and Orbot is installed and running, else false
+ */
+ public static boolean putOrbotInRequiredState(final int middleButton,
+ final Runnable middleButtonRunnable,
+ final Runnable dialogDismissRunnable,
+ Preferences.ProxyPrefs proxyPrefs,
+ FragmentActivity fragmentActivity) {
+
+ if (!proxyPrefs.torEnabled) {
+ return true;
+ }
+
+ if (!OrbotHelper.isOrbotInstalled(fragmentActivity)) {
+ Handler ignoreTorHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SupportInstallDialogFragment.MESSAGE_MIDDLE_CLICKED:
+ middleButtonRunnable.run();
+ break;
+ case SupportInstallDialogFragment.MESSAGE_DIALOG_DISMISSED:
+ dialogDismissRunnable.run();
+ break;
+ }
+ }
+ };
+
+ OrbotHelper.getInstallDialogFragmentWithThirdButton(
+ new Messenger(ignoreTorHandler),
+ middleButton
+ ).show(fragmentActivity.getSupportFragmentManager(), "OrbotHelperOrbotInstallDialog");
+
+ return false;
+ } else if (!OrbotHelper.isOrbotRunning()) {
+
+ Handler ignoreTorHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case OrbotStartDialogFragment.MESSAGE_MIDDLE_BUTTON:
+ middleButtonRunnable.run();
+ break;
+ case OrbotStartDialogFragment.MESSAGE_DIALOG_DISMISSED:
+ dialogDismissRunnable.run();
+ break;
+ }
+ }
+ };
+
+ OrbotHelper.getOrbotStartDialogFragment(new Messenger(ignoreTorHandler),
+ middleButton)
+ .show(fragmentActivity.getSupportFragmentManager(), "OrbotHelperOrbotStartDialog");
+
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public static boolean putOrbotInRequiredState(final int middleButton,
+ final Runnable middleButtonRunnable,
+ Preferences.ProxyPrefs proxyPrefs,
+ FragmentActivity fragmentActivity) {
+ Runnable emptyRunnable = new Runnable() {
+ @Override
+ public void run() {
+
+ }
+ };
+ return putOrbotInRequiredState(middleButton, middleButtonRunnable, emptyRunnable,
+ proxyPrefs, fragmentActivity);
+ }
+
+ /**
+ * generates a standard Orbot install/enable dialog if necessary, based on proxy settings in
+ * preferences
+ *
+ * @param ignoreTorRunnable run when the "Ignore Tor" button is pressed
+ * @param fragmentActivity used to start the activ
+ * @return
+ */
+ public static boolean putOrbotInRequiredState(Runnable ignoreTorRunnable,
+ FragmentActivity fragmentActivity) {
+ return putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTorRunnable,
+ Preferences.getPreferences(fragmentActivity).getProxyPrefs(), fragmentActivity);
+ }
+
+ public static boolean putOrbotInRequiredState(Runnable ignoreTorRunnable,
+ Runnable dismissDialogRunnable,
+ FragmentActivity fragmentActivity) {
+ return putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTorRunnable,
+ dismissDialogRunnable,
+ Preferences.getPreferences(fragmentActivity).getProxyPrefs(),
+ fragmentActivity);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java
new file mode 100644
index 000000000..d11e80f7a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java
@@ -0,0 +1,144 @@
+/* This is the license for Orlib, a free software project to
+ provide anonymity on the Internet from a Google Android smartphone.
+
+ For more information about Orlib, see https://guardianproject.info/
+
+ If you got this file as a part of a larger bundle, there may be other
+ license terms that you should be aware of.
+ ===============================================================================
+ Orlib is distributed under this license (aka the 3-clause BSD license)
+
+ Copyright (c) 2009-2010, Nathan Freitas, The Guardian Project
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the names of the copyright owners nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ *****
+ Orlib contains a binary distribution of the JSocks library:
+ http://code.google.com/p/jsocks-mirror/
+ which is licensed under the GNU Lesser General Public License:
+ http://www.gnu.org/licenses/lgpl.html
+
+ *****
+*/
+
+package org.sufficientlysecure.keychain.util.orbot;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.net.URLEncoder;
+import java.util.StringTokenizer;
+
+/**
+ * This class is taken from the NetCipher library: https://github.com/guardianproject/NetCipher/
+ */
+public class TorServiceUtils {
+ // various console cmds
+ public final static String SHELL_CMD_PS = "ps";
+ public final static String SHELL_CMD_PIDOF = "pidof";
+
+ public static int findProcessId(String command) {
+ int procId = -1;
+
+ try {
+ procId = findProcessIdWithPidOf(command);
+
+ if (procId == -1) {
+ procId = findProcessIdWithPS(command);
+ }
+ } catch (Exception e) {
+ try {
+ procId = findProcessIdWithPS(command);
+ } catch (Exception e2) {
+ Log.e(Constants.TAG, "Unable to get proc id for command: " + URLEncoder.encode(command), e2);
+ }
+ }
+
+ return procId;
+ }
+
+ // use 'pidof' command
+ public static int findProcessIdWithPidOf(String command) throws Exception {
+
+ int procId = -1;
+ Runtime r = Runtime.getRuntime();
+ Process procPs;
+
+ String baseName = new File(command).getName();
+ // fix contributed my mikos on 2010.12.10
+ procPs = r.exec(new String[]{
+ SHELL_CMD_PIDOF, baseName
+ });
+ // procPs = r.exec(SHELL_CMD_PIDOF);
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+
+ try {
+ // this line should just be the process id
+ procId = Integer.parseInt(line.trim());
+ break;
+ } catch (NumberFormatException e) {
+ Log.e("TorServiceUtils", "unable to parse process pid: " + line, e);
+ }
+ }
+
+ return procId;
+ }
+
+ // use 'ps' command
+ public static int findProcessIdWithPS(String command) throws Exception {
+ int procId = -1;
+ Runtime r = Runtime.getRuntime();
+ Process procPs;
+
+ procPs = r.exec(SHELL_CMD_PS);
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.contains(' ' + command)) {
+
+ StringTokenizer st = new StringTokenizer(line, " ");
+ st.nextToken(); // proc owner
+
+ procId = Integer.parseInt(st.nextToken().trim());
+
+ break;
+ }
+ }
+
+ return procId;
+ }
+}