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/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java76
-rw-r--r--OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java278
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java60
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java29
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/AppCompatPreferenceActivity.java127
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java82
-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.java21
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java128
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java48
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java20
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java82
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java385
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java583
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java574
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java177
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java49
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java107
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java85
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java25
-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.java19
-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.java89
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java99
-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.java20
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java41
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java78
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java59
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java91
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConstants.java114
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java162
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java)515
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java69
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java339
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java262
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java38
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java74
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java26
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java19
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java52
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java110
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java121
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java115
-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.java262
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java23
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java16
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java152
-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/remote/ui/SelectSignKeyIdActivity.java25
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CloudImportService.java384
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ConsolidateInputParcel.java58
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DeleteKeyringParcel.java63
-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.java111
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java74
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeybaseVerificationParcel.java62
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java752
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java221
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java516
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java159
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java66
-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.java67
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java129
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java83
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java131
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java200
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java47
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java165
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java103
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java147
-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.java420
-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.java16
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java90
-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)141
-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)4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java214
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java126
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java313
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java228
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java945
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java232
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java234
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java426
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java94
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java164
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java173
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java103
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java67
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java92
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java624
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java153
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeFragment.java19
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeSymmetricFragment.java86
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java88
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java336
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java24
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java178
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java60
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java174
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java430
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java43
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java184
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java245
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java173
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java270
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java575
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java9
-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.java137
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java523
-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.java360
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java96
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java779
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java46
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java287
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java)402
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java92
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java29
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java48
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java136
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java87
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java13
-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.java51
-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.java47
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java549
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java59
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java172
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java348
-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)181
-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.java190
-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.java23
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java163
-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.java78
-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.java78
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java30
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java99
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java100
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java126
-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.java247
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java61
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java83
-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.java43
-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/EncryptKeyCompletionView.java13
-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.java227
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java30
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java40
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java76
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java13
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java69
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java124
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java271
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OrientationUtils.java85
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java25
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableHashMap.java63
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java91
-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.java205
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java1
-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.java463
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java156
220 files changed, 17712 insertions, 9384 deletions
diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java
new file mode 100644
index 000000000..d35f1d751
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
+ *
+ * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
+ */
+
+package org.spongycastle.openpgp.operator.jcajce;
+
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.spongycastle.openpgp.operator.PGPDataDecryptor;
+import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+public class CachingDataDecryptorFactory implements PublicKeyDataDecryptorFactory
+{
+ private final PublicKeyDataDecryptorFactory mWrappedDecryptor;
+ private final Map<ByteBuffer, byte[]> mSessionKeyCache;
+
+ private OperatorHelper mOperatorHelper;
+
+ public CachingDataDecryptorFactory(String providerName,
+ final Map<ByteBuffer,byte[]> sessionKeyCache)
+ {
+ mWrappedDecryptor = null;
+ mSessionKeyCache = sessionKeyCache;
+
+ mOperatorHelper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+ }
+
+ public CachingDataDecryptorFactory(PublicKeyDataDecryptorFactory wrapped,
+ final Map<ByteBuffer,byte[]> sessionKeyCache)
+ {
+ mWrappedDecryptor = wrapped;
+ mSessionKeyCache = sessionKeyCache;
+
+ }
+
+ public boolean hasCachedSessionData(PGPPublicKeyEncryptedData encData) throws PGPException {
+ ByteBuffer bi = ByteBuffer.wrap(encData.getSessionKey()[0]);
+ return mSessionKeyCache.containsKey(bi);
+ }
+
+ public Map<ByteBuffer, byte[]> getCachedSessionKeys() {
+ return mSessionKeyCache;
+ }
+
+ public boolean canDecrypt() {
+ return mWrappedDecryptor != null;
+ }
+
+ @Override
+ public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException {
+ ByteBuffer bi = ByteBuffer.wrap(secKeyData[0]); // encoded MPI
+ if (mSessionKeyCache.containsKey(bi)) {
+ return mSessionKeyCache.get(bi);
+ }
+
+ byte[] sessionData = mWrappedDecryptor.recoverSessionData(keyAlgorithm, secKeyData);
+ mSessionKeyCache.put(bi, sessionData);
+ return sessionData;
+ }
+
+ @Override
+ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+ throws PGPException {
+ if (mWrappedDecryptor != null) {
+ return mWrappedDecryptor.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+ }
+ return mOperatorHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java
deleted file mode 100644
index 067bb3e19..000000000
--- a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/**
- * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
- *
- * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
- */
-
-package org.spongycastle.openpgp.operator.jcajce;
-
-import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
-import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
-import org.spongycastle.jcajce.util.NamedJcaJceHelper;
-import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
-import org.spongycastle.openpgp.PGPException;
-import org.spongycastle.openpgp.PGPPublicKey;
-import org.spongycastle.openpgp.operator.PGPDataDecryptor;
-import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
-
-import java.nio.ByteBuffer;
-import java.security.Provider;
-import java.util.Map;
-
-
-/**
- * This class is based on JcePublicKeyDataDecryptorFactoryBuilder
- *
- */
-public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
-{
- private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
- private OperatorHelper contentHelper = new OperatorHelper(new DefaultJcaJceHelper());
- private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
-// private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
-// private JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator();
-
- public static class NfcInteractionNeeded extends RuntimeException
- {
- public byte[] encryptedSessionKey;
-
- public NfcInteractionNeeded(byte[] encryptedSessionKey)
- {
- super("NFC interaction required!");
- this.encryptedSessionKey = encryptedSessionKey;
- }
- }
-
- public NfcSyncPublicKeyDataDecryptorFactoryBuilder()
- {
- }
-
- /**
- * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces.
- *
- * @param provider provider object for cryptographic primitives.
- * @return the current builder.
- */
- public NfcSyncPublicKeyDataDecryptorFactoryBuilder setProvider(Provider provider)
- {
- this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
- keyConverter.setProvider(provider);
- this.contentHelper = helper;
-
- return this;
- }
-
- /**
- * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces.
- *
- * @param providerName the name of the provider to reference for cryptographic primitives.
- * @return the current builder.
- */
- public NfcSyncPublicKeyDataDecryptorFactoryBuilder setProvider(String providerName)
- {
- this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
- keyConverter.setProvider(providerName);
- this.contentHelper = helper;
-
- return this;
- }
-
- public NfcSyncPublicKeyDataDecryptorFactoryBuilder setContentProvider(Provider provider)
- {
- this.contentHelper = new OperatorHelper(new ProviderJcaJceHelper(provider));
-
- return this;
- }
-
- public NfcSyncPublicKeyDataDecryptorFactoryBuilder setContentProvider(String providerName)
- {
- this.contentHelper = new OperatorHelper(new NamedJcaJceHelper(providerName));
-
- return this;
- }
-
- public PublicKeyDataDecryptorFactory build(final Map<ByteBuffer,byte[]> nfcDecryptedMap) {
- return new PublicKeyDataDecryptorFactory()
- {
- public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
- throws PGPException
- {
- if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH)
- {
- throw new PGPException("ECDH not supported!");
- }
-
- return decryptSessionData(keyAlgorithm, secKeyData, nfcDecryptedMap);
- }
-
- public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
- throws PGPException
- {
- return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
- }
- };
- }
-
-// public PublicKeyDataDecryptorFactory build(final PrivateKey privKey)
-// {
-// return new PublicKeyDataDecryptorFactory()
-// {
-// public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
-// throws PGPException
-// {
-// if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH)
-// {
-// throw new PGPException("ECDH requires use of PGPPrivateKey for decryption");
-// }
-// return decryptSessionData(keyAlgorithm, privKey, secKeyData);
-// }
-//
-// public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
-// throws PGPException
-// {
-// return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
-// }
-// };
-// }
-
-// public PublicKeyDataDecryptorFactory build(final PGPPrivateKey privKey, final byte[] nfcDecrypted)
-// {
-// return new PublicKeyDataDecryptorFactory()
-// {
-// public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
-// throws PGPException
-// {
-// if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH)
-// {
-// return decryptSessionData(privKey.getPrivateKeyDataPacket(), privKey.getPublicKeyPacket(), secKeyData);
-// }
-//
-// return decryptSessionData(keyAlgorithm, keyConverter.getPrivateKey(privKey), secKeyData, nfcDecrypted);
-// }
-//
-// public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
-// throws PGPException
-// {
-// return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
-// }
-// };
-// }
-
-// private byte[] decryptSessionData(BCPGKey privateKeyPacket, PublicKeyPacket pubKeyData, byte[][] secKeyData)
-// throws PGPException
-// {
-// ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKeyData.getKey();
-// X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID());
-//
-// byte[] enc = secKeyData[0];
-//
-// int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8;
-// byte[] pEnc = new byte[pLen];
-//
-// System.arraycopy(enc, 2, pEnc, 0, pLen);
-//
-// byte[] keyEnc = new byte[enc[pLen + 2]];
-//
-// System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.length);
-//
-// Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm());
-//
-// ECPoint S = x9Params.getCurve().decodePoint(pEnc).multiply(((ECSecretBCPGKey)privateKeyPacket).getX()).normalize();
-//
-// RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm());
-// Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, fingerprintCalculator.calculateFingerprint(pubKeyData)), "AESWrap");
-//
-// try
-// {
-// c.init(Cipher.UNWRAP_MODE, key);
-//
-// Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY);
-//
-// return PGPPad.unpadSessionData(paddedSessionKey.getEncoded());
-// }
-// catch (InvalidKeyException e)
-// {
-// throw new PGPException("error setting asymmetric cipher", e);
-// }
-// catch (NoSuchAlgorithmException e)
-// {
-// throw new PGPException("error setting asymmetric cipher", e);
-// }
-// }
-
- private byte[] decryptSessionData(int keyAlgorithm, byte[][] secKeyData,
- Map<ByteBuffer,byte[]> nfcDecryptedMap)
- throws PGPException
- {
-// Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm);
-//
-// try
-// {
-// c1.init(Cipher.DECRYPT_MODE, privKey);
-// }
-// catch (InvalidKeyException e)
-// {
-// throw new PGPException("error setting asymmetric cipher", e);
-// }
-
- if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT
- || keyAlgorithm == PGPPublicKey.RSA_GENERAL)
- {
- ByteBuffer bi = ByteBuffer.wrap(secKeyData[0]); // encoded MPI
-
- if (nfcDecryptedMap.containsKey(bi)) {
- return nfcDecryptedMap.get(bi);
- } else {
- // catch this when decryptSessionData() is executed and divert digest to card,
- // when doing the operation again reuse nfcDecrypted
- throw new NfcInteractionNeeded(bi.array());
- }
-
-// c1.update(bi, 2, bi.length - 2);
- }
- else
- {
- throw new PGPException("ElGamal not supported!");
-
-// ElGamalKey k = (ElGamalKey)privKey;
-// int size = (k.getParameters().getP().bitLength() + 7) / 8;
-// byte[] tmp = new byte[size];
-//
-// byte[] bi = secKeyData[0]; // encoded MPI
-// if (bi.length - 2 > size) // leading Zero? Shouldn't happen but...
-// {
-// c1.update(bi, 3, bi.length - 3);
-// }
-// else
-// {
-// System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2);
-// c1.update(tmp);
-// }
-//
-// bi = secKeyData[1]; // encoded MPI
-// for (int i = 0; i != tmp.length; i++)
-// {
-// tmp[i] = 0;
-// }
-//
-// if (bi.length - 2 > size) // leading Zero? Shouldn't happen but...
-// {
-// c1.update(bi, 3, bi.length - 3);
-// }
-// else
-// {
-// System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2);
-// c1.update(tmp);
-// }
- }
-
-// try
-// {
-// return c1.doFinal();
-// }
-// catch (Exception e)
-// {
-// throw new PGPException("exception decrypting session data", e);
-// }
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
index fc1cb8acc..6a9656b28 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
@@ -19,26 +19,31 @@ package org.sufficientlysecure.keychain;
import android.os.Environment;
-import org.spongycastle.bcpg.HashAlgorithmTags;
-import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import java.io.File;
+import java.net.Proxy;
public final class Constants {
public static final boolean DEBUG = BuildConfig.DEBUG;
public static final boolean DEBUG_LOG_DB_QUERIES = false;
public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false;
+ public static final boolean DEBUG_KEYSERVER_SYNC = false;
- public static final String TAG = "Keychain";
+ public static final String TAG = DEBUG ? "Keychain D" : "Keychain";
public static final String PACKAGE_NAME = "org.sufficientlysecure.keychain";
- public static final String ACCOUNT_NAME = "OpenKeychain";
- public static final String ACCOUNT_TYPE = PACKAGE_NAME + ".account";
+ public static final String ACCOUNT_NAME = DEBUG ? "OpenKeychain D" : "OpenKeychain";
+ public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key";
+ public static final String PROVIDER_AUTHORITY = BuildConfig.PROVIDER_CONTENT_AUTHORITY;
+ 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";
@@ -72,6 +77,11 @@ public final class Constants {
public static final File APP_DIR_FILE = new File(APP_DIR, "export.asc");
}
+ public static final class Notification {
+ public static final int PASSPHRASE_CACHE = 1;
+ public static final int KEYSERVER_SYNC_FAIL_ORBOT = 2;
+ }
+
public static final class Pref {
public static final String PASSPHRASE_CACHE_TTL = "passphraseCacheTtl";
public static final String PASSPHRASE_CACHE_SUBS = "passphraseCacheSubs";
@@ -84,11 +94,49 @@ public final class Constants {
public static final String SEARCH_KEYBASE = "search_keybase_pref";
public static final String USE_DEFAULT_YUBIKEY_PIN = "useDefaultYubikeyPin";
public static final String USE_NUMKEYPAD_FOR_YUBIKEY_PIN = "useNumKeypadForYubikeyPin";
+ public static final String ENCRYPT_FILENAMES = "encryptFilenames";
+ 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";
+ // keyserver sync settings
+ public static final String SYNC_CONTACTS = "syncContacts";
+ public static final String SYNC_KEYSERVER = "syncKeyserver";
+ // other settings
+ public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm";
+ public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities";
+ public static final String EXPERIMENTAL_ENABLE_KEYBASE = "experimentalEnableKeybase";
+
+ 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;
+ }
+
+ public static final class ProxyType {
+ public static final String TYPE_HTTP = "proxyHttp";
+ public static final String TYPE_SOCKS = "proxySocks";
+ }
+ }
+
+ /**
+ * 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 = 6;
}
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 710dbf8aa..311ef2d3b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
@@ -23,7 +23,6 @@ import android.app.Application;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
@@ -33,8 +32,11 @@ import android.provider.ContactsContract;
import android.widget.Toast;
import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
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;
@@ -89,18 +91,21 @@ 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(this);
TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer");
TemporaryStorageProvider.cleanUp(this);
- checkConsolidateRecovery();
+ if (!checkConsolidateRecovery()) {
+ // force DB upgrade, https://github.com/open-keychain/open-keychain/issues/1334
+ new KeychainDatabase(this).getReadableDatabase().close();
+ }
}
public static HashMap<String,Bitmap> qrCodeCache = new HashMap<>();
@@ -117,29 +122,33 @@ public class KeychainApplication extends Application {
/**
* Restart consolidate process if it has been interruped before
*/
- public void checkConsolidateRecovery() {
+ public boolean checkConsolidateRecovery() {
if (Preferences.getPreferences(this).getCachedConsolidate()) {
Intent consolidateIntent = new Intent(this, ConsolidateDialogActivity.class);
consolidateIntent.putExtra(ConsolidateDialogActivity.EXTRA_CONSOLIDATE_RECOVERY, true);
consolidateIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(consolidateIntent);
+ return true;
+ } else {
+ return false;
}
}
/**
- * Add OpenKeychain account to Android to link contacts with keys
- *
- * @param context
+ * Add OpenKeychain account to Android to link contacts with keys and keyserver sync
*/
public static void setupAccountAsNeeded(Context context) {
try {
AccountManager manager = AccountManager.get(context);
Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE);
- if (accounts == null || accounts.length == 0) {
+
+ if (accounts.length == 0) {
Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE);
if (manager.addAccountExplicitly(account, null, null)) {
+ // for contact sync
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
+ KeyserverSyncAdapterService.enableKeyserverSync(context);
} else {
Log.e(Constants.TAG, "Adding account failed!");
}
@@ -165,7 +174,7 @@ public class KeychainApplication extends Application {
int edgeDrawableId = context.getResources().getIdentifier("overscroll_edge", "drawable", "android");
Drawable androidEdge = context.getResources().getDrawable(edgeDrawableId);
androidEdge.setColorFilter(brandColor, PorterDuff.Mode.SRC_IN);
- } catch (Resources.NotFoundException e) {
+ } catch (Exception ignored) {
}
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/AppCompatPreferenceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/AppCompatPreferenceActivity.java
new file mode 100644
index 000000000..5200b8ced
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/AppCompatPreferenceActivity.java
@@ -0,0 +1,127 @@
+
+/*
+ * 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.compatibility;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
+ * to be used with AppCompat.
+ * <p/>
+ * This technique can be used with an {@link android.app.Activity} class, not just
+ * {@link android.preference.PreferenceActivity}.
+ */
+public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
+ private AppCompatDelegate mDelegate;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getDelegate().installViewFactory();
+ getDelegate().onCreate(savedInstanceState);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ getDelegate().onPostCreate(savedInstanceState);
+ }
+
+ public ActionBar getSupportActionBar() {
+ return getDelegate().getSupportActionBar();
+ }
+
+ public void setSupportActionBar(@Nullable Toolbar toolbar) {
+ getDelegate().setSupportActionBar(toolbar);
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ return getDelegate().getMenuInflater();
+ }
+
+ @Override
+ public void setContentView(@LayoutRes int layoutResID) {
+ getDelegate().setContentView(layoutResID);
+ }
+
+ @Override
+ public void setContentView(View view) {
+ getDelegate().setContentView(view);
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().setContentView(view, params);
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().addContentView(view, params);
+ }
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ getDelegate().onPostResume();
+ }
+
+ @Override
+ protected void onTitleChanged(CharSequence title, int color) {
+ super.onTitleChanged(title, color);
+ getDelegate().setTitle(title);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ getDelegate().onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ getDelegate().onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ getDelegate().onDestroy();
+ }
+
+ public void invalidateOptionsMenu() {
+ getDelegate().invalidateOptionsMenu();
+ }
+
+ private AppCompatDelegate getDelegate() {
+ if (mDelegate == null) {
+ mDelegate = AppCompatDelegate.create(this, null);
+ }
+ return mDelegate;
+ }
+} \ No newline at end of file
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 fa3600ffb..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,79 +17,35 @@
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";
-
- /**
- * Wrapper around ClipboardManager based on Android version using Reflection API
- *
- * @param context
- * @param text
- */
- public static void copyToClipboard(Context context, String text) {
- Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE);
- try {
- if ("android.text.ClipboardManager".equals(clipboard.getClass().getName())) {
- Method methodSetText = clipboard.getClass()
- .getMethod("setText", CharSequence.class);
- methodSetText.invoke(clipboard, text);
- } else if ("android.content.ClipboardManager".equals(clipboard.getClass().getName())) {
- Class<?> classClipData = Class.forName("android.content.ClipData");
- Method methodNewPlainText = classClipData.getMethod("newPlainText",
- CharSequence.class, CharSequence.class);
- Object clip = methodNewPlainText.invoke(null, clipboardLabel, text);
- methodNewPlainText = clipboard.getClass()
- .getMethod("setPrimaryClip", classClipData);
- methodNewPlainText.invoke(clipboard, clip);
- }
- } catch (Exception e) {
- Log.e(Constants.TAG, "There was an error copying the text to the clipboard", e);
+ @Nullable
+ public static String getClipboardText(@Nullable Context context) {
+ if (context == null) {
+ return null;
}
- }
-
- /**
- * Wrapper around ClipboardManager based on Android version using Reflection API
- *
- * @param context
- */
- public static CharSequence getClipboardText(Context context) {
- Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE);
- try {
- if ("android.text.ClipboardManager".equals(clipboard.getClass().getName())) {
- // CharSequence text = clipboard.getText();
- Method methodGetText = clipboard.getClass().getMethod("getText");
- Object text = methodGetText.invoke(clipboard);
+ ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
- return (CharSequence) text;
- } else if ("android.content.ClipboardManager".equals(clipboard.getClass().getName())) {
- // ClipData clipData = clipboard.getPrimaryClip();
- Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip");
- Object clipData = methodGetPrimaryClip.invoke(clipboard);
-
- // ClipData.Item clipDataItem = clipData.getItemAt(0);
- Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", int.class);
- Object clipDataItem = methodGetItemAt.invoke(clipData, 0);
-
- // CharSequence text = clipDataItem.coerceToText(context);
- Method methodGetString = clipDataItem.getClass().getMethod("coerceToText",
- Context.class);
- Object text = methodGetString.invoke(clipDataItem, context);
-
- return (CharSequence) text;
- } else {
- return null;
- }
- } catch (Exception e) {
- Log.e(Constants.TAG, "There was an error getting the text from the clipboard", e);
+ ClipData clip = clipboard.getPrimaryClip();
+ if (clip == null || clip.getItemCount() == 0) {
+ Log.e(Constants.TAG, "No clipboard data!");
return null;
}
+
+ ClipData.Item item = clip.getItemAt(0);
+ CharSequence seq = item.coerceToText(context);
+ if (seq != null) {
+ return seq.toString();
+ }
+ return null;
}
}
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..558b8ce7d 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 url to be queried by client
+ * @param proxy proxy to be used by client
+ * @return client with a pinned certificate if necesary
+ */
+ 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.d(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 a824e73d7..e4026eaaf 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java
@@ -18,17 +18,22 @@
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;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.util.concurrent.atomic.AtomicBoolean;
-public abstract class BaseOperation implements PassphraseCacheInterface {
+public abstract class BaseOperation <T extends Parcelable> implements PassphraseCacheInterface {
final public Context mContext;
final public Progressable mProgressable;
@@ -40,7 +45,7 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
* of common methods for progress, cancellation and passphrase cache handling.
*
* An "operation" in this sense is a high level operation which is called
- * by the KeychainIntentService or OpenPgpService services. Concrete
+ * by the KeychainService or OpenPgpService services. Concrete
* subclasses of this class should implement either a single or a group of
* related operations. An operation must rely solely on its input
* parameters for operation specifics. It should also write a log of its
@@ -49,7 +54,7 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
*
* An operation must *not* throw exceptions of any kind, errors should be
* handled as part of the OperationResult! Consequently, all handling of
- * errors in KeychainIntentService and OpenPgpService should consist of
+ * errors in KeychainService and OpenPgpService should consist of
* informational rather than operational means.
*
* Note that subclasses of this class should be either Android- or
@@ -73,6 +78,9 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
mCancelled = cancelled;
}
+ @NonNull
+ public abstract OperationResult execute(T input, CryptoInputParcel cryptoInput);
+
public void updateProgress(int message, int current, int total) {
if (mProgressable != null) {
mProgressable.setProgress(message, current, total);
@@ -104,8 +112,11 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
@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 051517abd..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,28 +44,33 @@ 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 {
+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);
}
- public CertifyResult certify(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput, String keyServerUri) {
+ @NonNull
+ @Override
+ public CertifyResult execute(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_CRT, 0);
@@ -79,14 +85,46 @@ public class CertifyOperation extends BaseOperation {
log.add(LogType.MSG_CRT_UNLOCK, 1);
certificationKey = secretKeyRing.getSecretKey();
- if (!cryptoInput.hasPassphrase()) {
- return new CertifyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
- certificationKey.getKeyId(), certificationKey.getKeyId(), null));
+ Passphrase passphrase;
+
+ switch (certificationKey.getSecretKeyType()) {
+ case PIN:
+ case PATTERN:
+ case PASSPHRASE:
+ 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),
+ cryptoInput
+ );
+ }
+ break;
+
+ case PASSPHRASE_EMPTY:
+ passphrase = new Passphrase("");
+ break;
+
+ case DIVERT_TO_CARD:
+ // the unlock operation will succeed for passphrase == null in a divertToCard key
+ passphrase = null;
+ break;
+
+ default:
+ log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
+ return new CertifyResult(CertifyResult.RESULT_ERROR, log);
}
- // certification is always with the master key id, so use that one
- Passphrase passphrase = cryptoInput.getPassphrase();
-
if (!certificationKey.unlock(passphrase)) {
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
@@ -152,9 +190,9 @@ public class CertifyOperation extends BaseOperation {
}
- 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);
@@ -165,11 +203,24 @@ public class CertifyOperation extends BaseOperation {
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;
- ImportExportOperation importExportOperation = null;
- if (keyServerUri != null) {
- keyServer = new HkpKeyserver(keyServerUri);
- importExportOperation = new ImportExportOperation(mContext, mProviderHelper, mProgressable);
+ 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
@@ -178,7 +229,8 @@ public class CertifyOperation extends BaseOperation {
// 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,
@@ -187,13 +239,16 @@ public class CertifyOperation extends BaseOperation {
mProviderHelper.clearLog();
SaveKeyringResult result = mProviderHelper.savePublicKeyRing(certifiedKey);
- if (importExportOperation != null) {
- // TODO use subresult, get rid of try/catch!
- try {
- importExportOperation.uploadKeyRingToServer(keyServer, certifiedKey);
+ if (exportOperation != null) {
+ 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;
}
}
@@ -205,19 +260,24 @@ public class CertifyOperation extends BaseOperation {
}
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
new file mode 100644
index 000000000..782cd6800
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 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.support.annotation.NonNull;
+
+import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+
+public class ConsolidateOperation extends BaseOperation<ConsolidateInputParcel> {
+
+ public ConsolidateOperation(Context context, ProviderHelper providerHelper, Progressable
+ progressable) {
+ super(context, providerHelper, progressable);
+ }
+
+ @NonNull
+ @Override
+ public ConsolidateResult execute(ConsolidateInputParcel consolidateInputParcel,
+ CryptoInputParcel cryptoInputParcel) {
+ if (consolidateInputParcel.mConsolidateRecovery) {
+ return mProviderHelper.consolidateDatabaseStep2(mProgressable);
+ } else {
+ return mProviderHelper.consolidateDatabaseStep1(mProgressable);
+ }
+ }
+}
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 5ef04ab05..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,15 +18,19 @@
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;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
+import org.sufficientlysecure.keychain.service.DeleteKeyringParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
/** An operation which implements a high level keyring delete operation.
@@ -37,13 +41,24 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
* a list.
*
*/
-public class DeleteOperation extends BaseOperation {
+public class DeleteOperation extends BaseOperation<DeleteKeyringParcel> {
public DeleteOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
super(context, providerHelper, progressable);
}
- public DeleteResult execute(long[] masterKeyIds, boolean isSecret) {
+ @NonNull
+ @Override
+ 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();
@@ -104,7 +119,6 @@ public class DeleteOperation extends BaseOperation {
}
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 4072d91c5..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,17 +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.Passphrase;
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.
@@ -51,14 +56,23 @@ import java.util.concurrent.atomic.AtomicBoolean;
* @see SaveKeyringParcel
*
*/
-public class EditKeyOperation extends BaseOperation {
+public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
public EditKeyOperation(Context context, ProviderHelper providerHelper,
Progressable progressable, AtomicBoolean cancelled) {
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);
@@ -119,6 +133,36 @@ public class EditKeyOperation extends BaseOperation {
// 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));
@@ -130,15 +174,24 @@ public class EditKeyOperation extends BaseOperation {
}
// 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);
@@ -150,5 +203,4 @@ public class EditKeyOperation extends BaseOperation {
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
new file mode 100644
index 000000000..531ac01f2
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.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;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
+import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
+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.pgp.CanonicalizedKeyRing;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+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 org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
+
+/**
+ * An operation class which implements high level export
+ * operations.
+ * This class receives a source and/or destination of keys as input and performs
+ * all steps for this export.
+ *
+ * @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.
+ */
+public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
+
+ public ExportOperation(Context context, ProviderHelper providerHelper, Progressable
+ progressable) {
+ super(context, providerHelper, progressable);
+ }
+
+ public ExportOperation(Context context, ProviderHelper providerHelper,
+ Progressable progressable, AtomicBoolean cancelled) {
+ super(context, providerHelper, progressable, cancelled);
+ }
+
+ public ExportResult uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring,
+ Proxy proxy) {
+ return uploadKeyRingToServer(server, keyring.getUncachedKeyRing(), proxy);
+ }
+
+ 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, 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);
+
+ 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();
+ }
+ bos.close();
+ } catch (IOException e) {
+ // this is just a finally thing, no matter if it doesn't work out.
+ }
+ }
+ }
+
+ public ExportResult exportToFile(long[] masterKeyIds, boolean exportSecret, String outputFile) {
+
+ OperationLog log = new OperationLog();
+ if (masterKeyIds != null) {
+ log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length);
+ } else {
+ log.add(LogType.MSG_EXPORT_ALL, 0);
+ }
+
+ // do we have a file name?
+ if (outputFile == null) {
+ log.add(LogType.MSG_EXPORT_ERROR_NO_FILE, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
+
+ log.add(LogType.MSG_EXPORT_FILE_NAME, 1, outputFile);
+
+ // check if storage is ready
+ if (!FileHelper.isStorageMounted(outputFile)) {
+ log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
+
+ try {
+ OutputStream outStream = new FileOutputStream(outputFile);
+ try {
+ ExportResult result = exportKeyRings(log, masterKeyIds, exportSecret, outStream);
+ if (result.cancelled()) {
+ //noinspection ResultOfMethodCallIgnored
+ new File(outputFile).delete();
+ }
+ return result;
+ } finally {
+ outStream.close();
+ }
+ } catch (IOException e) {
+ log.add(LogType.MSG_EXPORT_ERROR_FOPEN, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
+
+ }
+
+ public ExportResult exportToUri(long[] masterKeyIds, boolean exportSecret, Uri outputUri) {
+
+ OperationLog log = new OperationLog();
+ if (masterKeyIds != null) {
+ log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length);
+ } else {
+ log.add(LogType.MSG_EXPORT_ALL, 0);
+ }
+
+ // do we have a file name?
+ if (outputUri == null) {
+ log.add(LogType.MSG_EXPORT_ERROR_NO_URI, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
+
+ try {
+ OutputStream outStream = mProviderHelper.getContentResolver().openOutputStream
+ (outputUri);
+ return exportKeyRings(log, masterKeyIds, exportSecret, outStream);
+ } catch (FileNotFoundException e) {
+ log.add(LogType.MSG_EXPORT_ERROR_URI_OPEN, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
+
+ }
+
+ ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret,
+ OutputStream outStream) {
+
+ /* TODO isn't this checked above, with the isStorageMounted call?
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
+ */
+
+ if (!BufferedOutputStream.class.isInstance(outStream)) {
+ outStream = new BufferedOutputStream(outStream);
+ }
+
+ int okSecret = 0, okPublic = 0, progress = 0;
+
+ Cursor cursor = null;
+ try {
+
+ String selection = null, selectionArgs[] = null;
+
+ if (masterKeyIds != null) {
+ // convert long[] to String[]
+ selectionArgs = new String[masterKeyIds.length];
+ for (int i = 0; i < masterKeyIds.length; i++) {
+ 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.KEYS + "." + KeyRings.MASTER_KEY_ID
+ + " IN (" + placeholders + ")";
+ }
+
+ cursor = mProviderHelper.getContentResolver().query(
+ KeyRings.buildUnifiedKeyRingsUri(), new String[]{
+ KeyRings.MASTER_KEY_ID, KeyRings.PUBKEY_DATA,
+ KeyRings.PRIVKEY_DATA, KeyRings.HAS_ANY_SECRET
+ }, selection, selectionArgs, Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
+ );
+
+ if (cursor == null || !cursor.moveToFirst()) {
+ log.add(LogType.MSG_EXPORT_ERROR_DB, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret);
+ }
+
+ int numKeys = cursor.getCount();
+
+ updateProgress(
+ mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
+ numKeys), 0, numKeys);
+
+ // For each public masterKey id
+ while (!cursor.isAfterLast()) {
+
+ long keyId = cursor.getLong(0);
+ ArmoredOutputStream arOutStream = null;
+
+ // Create an output stream
+ try {
+ arOutStream = new ArmoredOutputStream(outStream);
+
+ log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId));
+
+ byte[] data = cursor.getBlob(1);
+ CanonicalizedKeyRing ring =
+ UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
+ ring.encode(arOutStream);
+
+ okPublic += 1;
+ } catch (PgpGeneralException e) {
+ log.add(LogType.MSG_EXPORT_ERROR_KEY, 2);
+ updateProgress(progress++, numKeys);
+ continue;
+ } finally {
+ // make sure this is closed
+ if (arOutStream != null) {
+ arOutStream.close();
+ }
+ arOutStream = null;
+ }
+
+ if (exportSecret && cursor.getInt(3) > 0) {
+ try {
+ arOutStream = new ArmoredOutputStream(outStream);
+
+ // export secret key part
+ log.add(LogType.MSG_EXPORT_SECRET, 2, KeyFormattingUtils.beautifyKeyId
+ (keyId));
+ byte[] data = cursor.getBlob(2);
+ CanonicalizedKeyRing ring =
+ UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
+ ring.encode(arOutStream);
+
+ okSecret += 1;
+ } catch (PgpGeneralException e) {
+ log.add(LogType.MSG_EXPORT_ERROR_KEY, 2);
+ updateProgress(progress++, numKeys);
+ continue;
+ } finally {
+ // make sure this is closed
+ if (arOutStream != null) {
+ arOutStream.close();
+ }
+ }
+ }
+
+ updateProgress(progress++, numKeys);
+
+ cursor.moveToNext();
+ }
+
+ updateProgress(R.string.progress_done, numKeys, numKeys);
+
+ } catch (IOException e) {
+ log.add(LogType.MSG_EXPORT_ERROR_IO, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret);
+ } finally {
+ // Make sure the stream is closed
+ if (outStream != null) try {
+ outStream.close();
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "error closing stream", e);
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+
+ log.add(LogType.MSG_EXPORT_SUCCESS, 1);
+ return new ExportResult(ExportResult.RESULT_OK, log, okPublic, okSecret);
+
+ }
+
+ @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 {
+ 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());
+ }
+ }
+ case EXPORT_FILE: {
+ return exportToFile(exportInput.mMasterKeyIds, exportInput.mExportSecret,
+ exportInput.mOutputFile);
+ }
+ case EXPORT_URI: {
+ return exportToUri(exportInput.mMasterKeyIds, exportInput.mExportSecret,
+ exportInput.mOutputUri);
+ }
+ default: { // can never happen, all enum types must be handled above
+ throw new AssertionError("must not happen, this is a bug!");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java
deleted file mode 100644
index 86cfc21a3..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java
+++ /dev/null
@@ -1,583 +0,0 @@
-/*
- * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
- * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package org.sufficientlysecure.keychain.operations;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-
-import org.spongycastle.bcpg.ArmoredOutputStream;
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
-import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
-import org.sufficientlysecure.keychain.keyimport.Keyserver;
-import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
-import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
-import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
-import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
-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.CanonicalizedKeyRing;
-import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
-import org.sufficientlysecure.keychain.pgp.PgpHelper;
-import org.sufficientlysecure.keychain.pgp.Progressable;
-import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
-import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
-import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
-import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
-import org.sufficientlysecure.keychain.util.FileHelper;
-import org.sufficientlysecure.keychain.util.Log;
-import org.sufficientlysecure.keychain.util.ParcelableFileCache;
-import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
-import org.sufficientlysecure.keychain.util.ProgressScaler;
-
-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.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/** An operation class which implements high level import and export
- * operations.
- *
- * This class receives a source and/or destination of keys as input and performs
- * all steps for this import or export.
- *
- * For the import operation, the only valid source is an Iterator of
- * ParcelableKeyRing, each of which must contain either a single
- * keyring encoded as bytes, or a unique reference to a keyring
- * on keyservers and/or keybase.io.
- * It is important to note that public keys should generally be imported before
- * secret keys, because some implementations (notably Symantec PGP Desktop) do
- * not include self certificates for user ids in the secret keyring. The import
- * method here will generally import keyrings in the order given by the
- * iterator. so this should be ensured beforehand.
- * @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 ImportExportOperation extends BaseOperation {
-
- public ImportExportOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
- super(context, providerHelper, progressable);
- }
-
- public ImportExportOperation(Context context, ProviderHelper providerHelper,
- Progressable progressable, AtomicBoolean cancelled) {
- super(context, providerHelper, progressable, cancelled);
- }
-
- public void uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) throws AddKeyException {
- uploadKeyRingToServer(server, keyring.getUncachedKeyRing());
- }
-
- public void uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring) throws AddKeyException {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ArmoredOutputStream aos = null;
- try {
- aos = new ArmoredOutputStream(bos);
- keyring.encode(aos);
- aos.close();
-
- String armoredKey = bos.toString("UTF-8");
- server.add(armoredKey);
- } catch (IOException e) {
- Log.e(Constants.TAG, "IOException", e);
- throw new AddKeyException();
- } finally {
- try {
- if (aos != null) {
- aos.close();
- }
- bos.close();
- } catch (IOException e) {
- // this is just a finally thing, no matter if it doesn't work out.
- }
- }
- }
-
- public ImportKeyResult importKeyRings(List<ParcelableKeyRing> entries, String keyServerUri) {
-
- Iterator<ParcelableKeyRing> it = entries.iterator();
- int numEntries = entries.size();
-
- return importKeyRings(it, numEntries, keyServerUri);
-
- }
-
- public ImportKeyResult importKeyRings(ParcelableFileCache<ParcelableKeyRing> cache, String keyServerUri) {
-
- // get entries from cached file
- try {
- IteratorWithSize<ParcelableKeyRing> it = cache.readCache();
- int numEntries = it.getSize();
-
- return importKeyRings(it, numEntries, keyServerUri);
- } catch (IOException e) {
-
- // Special treatment here, we need a lot
- OperationLog log = new OperationLog();
- log.add(LogType.MSG_IMPORT, 0, 0);
- log.add(LogType.MSG_IMPORT_ERROR_IO, 0, 0);
-
- return new ImportKeyResult(ImportKeyResult.RESULT_ERROR, log);
- }
-
- }
-
- public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num, String keyServerUri) {
- updateProgress(R.string.progress_importing, 0, 100);
-
- OperationLog log = new OperationLog();
- log.add(LogType.MSG_IMPORT, 0, num);
-
- // If there aren't even any keys, do nothing here.
- if (entries == null || !entries.hasNext()) {
- return new ImportKeyResult(ImportKeyResult.RESULT_FAIL_NOTHING, log);
- }
-
- int newKeys = 0, updatedKeys = 0, badKeys = 0, secret = 0;
- ArrayList<Long> importedMasterKeyIds = new ArrayList<>();
-
- boolean cancelled = false;
- int position = 0;
- double progSteps = 100.0 / num;
-
- KeybaseKeyserver keybaseServer = null;
- HkpKeyserver keyServer = null;
-
- // iterate over all entries
- while (entries.hasNext()) {
- ParcelableKeyRing entry = entries.next();
-
- // Has this action been cancelled? If so, don't proceed any further
- if (checkCancelled()) {
- cancelled = true;
- break;
- }
-
- try {
-
- UncachedKeyRing key = null;
-
- // If there is already byte data, use that
- if (entry.mBytes != null) {
- key = UncachedKeyRing.decodeFromData(entry.mBytes);
- }
- // Otherwise, we need to fetch the data from a server first
- else {
-
- // We fetch from keyservers first, because we tend to get more certificates
- // from there, so the number of certificates which are merged in later is smaller.
-
- // If we have a keyServerUri and a fingerprint or at least a keyId,
- // download from HKP
- if (keyServerUri != null
- && (entry.mKeyIdHex != null || entry.mExpectedFingerprint != null)) {
- // Make sure we have the keyserver instance cached
- if (keyServer == null) {
- log.add(LogType.MSG_IMPORT_KEYSERVER, 1, keyServerUri);
- keyServer = new HkpKeyserver(keyServerUri);
- }
-
- try {
- byte[] data;
- // Download by fingerprint, or keyId - whichever is available
- if (entry.mExpectedFingerprint != null) {
- log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, "0x" + entry.mExpectedFingerprint.substring(24));
- data = keyServer.get("0x" + entry.mExpectedFingerprint).getBytes();
- } else {
- log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, entry.mKeyIdHex);
- data = keyServer.get(entry.mKeyIdHex).getBytes();
- }
- key = UncachedKeyRing.decodeFromData(data);
- if (key != null) {
- log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_OK, 3);
- } else {
- log.add(LogType.MSG_IMPORT_FETCH_ERROR_DECODE, 3);
- }
- } catch (Keyserver.QueryFailedException e) {
- Log.e(Constants.TAG, "query failed", e);
- log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3, e.getMessage());
- }
- }
-
- // If we have a keybase name, try to fetch from there
- if (entry.mKeybaseName != null) {
- // Make sure we have this cached
- if (keybaseServer == null) {
- keybaseServer = new KeybaseKeyserver();
- }
-
- try {
- log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.mKeybaseName);
- byte[] data = keybaseServer.get(entry.mKeybaseName).getBytes();
- key = UncachedKeyRing.decodeFromData(data);
-
- // If there already is a key (of keybase origin), merge the two
- if (key != null) {
- log.add(LogType.MSG_IMPORT_MERGE, 3);
- UncachedKeyRing merged = UncachedKeyRing.decodeFromData(data);
- merged = key.merge(merged, log, 4);
- // If the merge didn't fail, use the new merged key
- if (merged != null) {
- key = merged;
- }
- } else {
- log.add(LogType.MSG_IMPORT_FETCH_ERROR_DECODE, 3);
- key = UncachedKeyRing.decodeFromData(data);
- }
- } catch (Keyserver.QueryFailedException e) {
- // download failed, too bad. just proceed
- Log.e(Constants.TAG, "query failed", e);
- log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3);
- }
- }
- }
-
- if (key == null) {
- log.add(LogType.MSG_IMPORT_FETCH_ERROR, 2);
- badKeys += 1;
- continue;
- }
-
- // If we have an expected fingerprint, make sure it matches
- if (entry.mExpectedFingerprint != null) {
- if (!key.containsSubkey(entry.mExpectedFingerprint)) {
- log.add(LogType.MSG_IMPORT_FINGERPRINT_ERROR, 2);
- badKeys += 1;
- continue;
- } else {
- log.add(LogType.MSG_IMPORT_FINGERPRINT_OK, 2);
- }
- }
-
- // Another check if we have been cancelled
- if (checkCancelled()) {
- cancelled = true;
- break;
- }
-
- SaveKeyringResult result;
- mProviderHelper.clearLog();
- if (key.isSecret()) {
- result = mProviderHelper.saveSecretKeyRing(key,
- new ProgressScaler(mProgressable, (int)(position*progSteps), (int)((position+1)*progSteps), 100));
- } else {
- result = mProviderHelper.savePublicKeyRing(key,
- new ProgressScaler(mProgressable, (int)(position*progSteps), (int)((position+1)*progSteps), 100));
- }
- if (!result.success()) {
- badKeys += 1;
- } else if (result.updated()) {
- updatedKeys += 1;
- importedMasterKeyIds.add(key.getMasterKeyId());
- } else {
- newKeys += 1;
- if (key.isSecret()) {
- secret += 1;
- }
- importedMasterKeyIds.add(key.getMasterKeyId());
- }
-
- log.add(result, 2);
-
- } catch (IOException | PgpGeneralException e) {
- Log.e(Constants.TAG, "Encountered bad key on import!", e);
- ++badKeys;
- }
- // update progress
- position++;
- }
-
- // Special: consolidate on secret key import (cannot be cancelled!)
- if (secret > 0) {
- setPreventCancel();
- ConsolidateResult result = mProviderHelper.consolidateDatabaseStep1(mProgressable);
- log.add(result, 1);
- }
-
- // Special: make sure new data is synced into contacts
- // disabling sync right now since it reduces speed while multi-threading
- // so, we expect calling functions to take care of it. KeychainIntentService handles this
- //ContactSyncAdapterService.requestSync();
-
- // convert to long array
- long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()];
- for (int i = 0; i < importedMasterKeyIds.size(); ++i) {
- importedMasterKeyIdsArray[i] = importedMasterKeyIds.get(i);
- }
-
- int resultType = 0;
- if (cancelled) {
- log.add(LogType.MSG_OPERATION_CANCELLED, 1);
- resultType |= ImportKeyResult.RESULT_CANCELLED;
- }
-
- // special return case: no new keys at all
- if (badKeys == 0 && newKeys == 0 && updatedKeys == 0) {
- resultType = ImportKeyResult.RESULT_FAIL_NOTHING;
- } else {
- if (newKeys > 0) {
- resultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
- }
- if (updatedKeys > 0) {
- resultType |= ImportKeyResult.RESULT_OK_UPDATED;
- }
- if (badKeys > 0) {
- resultType |= ImportKeyResult.RESULT_WITH_ERRORS;
- if (newKeys == 0 && updatedKeys == 0) {
- resultType |= ImportKeyResult.RESULT_ERROR;
- }
- }
- if (log.containsWarnings()) {
- resultType |= ImportKeyResult.RESULT_WARNINGS;
- }
- }
-
- // Final log entry, it's easier to do this individually
- if ( (newKeys > 0 || updatedKeys > 0) && badKeys > 0) {
- log.add(LogType.MSG_IMPORT_PARTIAL, 1);
- } else if (newKeys > 0 || updatedKeys > 0) {
- log.add(LogType.MSG_IMPORT_SUCCESS, 1);
- } else {
- log.add(LogType.MSG_IMPORT_ERROR, 1);
- }
-
- ContactSyncAdapterService.requestSync();
-
- return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
- importedMasterKeyIdsArray);
- }
-
- public ExportResult exportToFile(long[] masterKeyIds, boolean exportSecret, String outputFile) {
-
- OperationLog log = new OperationLog();
- if (masterKeyIds != null) {
- log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length);
- } else {
- log.add(LogType.MSG_EXPORT_ALL, 0);
- }
-
- // do we have a file name?
- if (outputFile == null) {
- log.add(LogType.MSG_EXPORT_ERROR_NO_FILE, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
-
- // check if storage is ready
- if (!FileHelper.isStorageMounted(outputFile)) {
- log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
-
- try {
- OutputStream outStream = new FileOutputStream(outputFile);
- ExportResult result = exportKeyRings(log, masterKeyIds, exportSecret, outStream);
- if (result.cancelled()) {
- //noinspection ResultOfMethodCallIgnored
- new File(outputFile).delete();
- }
- return result;
- } catch (FileNotFoundException e) {
- log.add(LogType.MSG_EXPORT_ERROR_FOPEN, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
-
- }
-
- public ExportResult exportToUri(long[] masterKeyIds, boolean exportSecret, Uri outputUri) {
-
- OperationLog log = new OperationLog();
- if (masterKeyIds != null) {
- log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length);
- } else {
- log.add(LogType.MSG_EXPORT_ALL, 0);
- }
-
- // do we have a file name?
- if (outputUri == null) {
- log.add(LogType.MSG_EXPORT_ERROR_NO_URI, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
-
- try {
- OutputStream outStream = mProviderHelper.getContentResolver().openOutputStream(outputUri);
- return exportKeyRings(log, masterKeyIds, exportSecret, outStream);
- } catch (FileNotFoundException e) {
- log.add(LogType.MSG_EXPORT_ERROR_URI_OPEN, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
-
- }
-
- ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret,
- OutputStream outStream) {
-
- /* TODO isn't this checked above, with the isStorageMounted call?
- if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
- */
-
- if ( ! BufferedOutputStream.class.isInstance(outStream)) {
- outStream = new BufferedOutputStream(outStream);
- }
-
- int okSecret = 0, okPublic = 0, progress = 0;
-
- Cursor cursor = null;
- try {
-
- String selection = null, ids[] = null;
-
- if (masterKeyIds != null) {
- // generate placeholders and string selection args
- ids = new String[masterKeyIds.length];
- StringBuilder placeholders = new StringBuilder("?");
- for (int i = 0; i < masterKeyIds.length; i++) {
- ids[i] = Long.toString(masterKeyIds[i]);
- if (i != 0) {
- placeholders.append(",?");
- }
- }
-
- // put together selection string
- selection = Tables.KEY_RINGS_PUBLIC + "." + KeyRings.MASTER_KEY_ID
- + " IN (" + placeholders + ")";
- }
-
- cursor = mProviderHelper.getContentResolver().query(
- 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
- );
-
- if (cursor == null || !cursor.moveToFirst()) {
- log.add(LogType.MSG_EXPORT_ERROR_DB, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret);
- }
-
- int numKeys = cursor.getCount();
-
- updateProgress(
- mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
- numKeys), 0, numKeys);
-
- // For each public masterKey id
- while (!cursor.isAfterLast()) {
-
- long keyId = cursor.getLong(0);
- ArmoredOutputStream arOutStream = null;
-
- // Create an output stream
- try {
- arOutStream = new ArmoredOutputStream(outStream);
-
- log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId));
-
- byte[] data = cursor.getBlob(1);
- CanonicalizedKeyRing ring =
- UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
- ring.encode(arOutStream);
-
- okPublic += 1;
- } catch (PgpGeneralException e) {
- log.add(LogType.MSG_EXPORT_ERROR_KEY, 2);
- updateProgress(progress++, numKeys);
- continue;
- } finally {
- // make sure this is closed
- if (arOutStream != null) {
- arOutStream.close();
- }
- arOutStream = null;
- }
-
- if (exportSecret && cursor.getInt(3) > 0) {
- try {
- arOutStream = new ArmoredOutputStream(outStream);
-
- // export secret key part
- log.add(LogType.MSG_EXPORT_SECRET, 2, KeyFormattingUtils.beautifyKeyId(keyId));
- byte[] data = cursor.getBlob(2);
- CanonicalizedKeyRing ring =
- UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
- ring.encode(arOutStream);
-
- okSecret += 1;
- } catch (PgpGeneralException e) {
- log.add(LogType.MSG_EXPORT_ERROR_KEY, 2);
- updateProgress(progress++, numKeys);
- continue;
- } finally {
- // make sure this is closed
- if (arOutStream != null) {
- arOutStream.close();
- }
- }
- }
-
- updateProgress(progress++, numKeys);
-
- cursor.moveToNext();
- }
-
- updateProgress(R.string.progress_done, numKeys, numKeys);
-
- } catch (IOException e) {
- log.add(LogType.MSG_EXPORT_ERROR_IO, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret);
- } finally {
- // Make sure the stream is closed
- if (outStream != null) try {
- outStream.close();
- } catch (Exception e) {
- Log.e(Constants.TAG, "error closing stream", e);
- }
- if (cursor != null) {
- cursor.close();
- }
- }
-
-
- log.add(LogType.MSG_EXPORT_SUCCESS, 1);
- return new ExportResult(ExportResult.RESULT_OK, log, okPublic, okSecret);
-
- }
-
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java
new file mode 100644
index 000000000..7b224fe8e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.operations;
+
+
+import java.io.IOException;
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+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;
+import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
+import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
+import org.sufficientlysecure.keychain.keyimport.Keyserver;
+import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
+import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
+import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
+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.SaveKeyringResult;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+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 org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
+
+/**
+ * An operation class which implements high level import
+ * operations.
+ * This class receives a source and/or destination of keys as input and performs
+ * all steps for this import.
+ * For the import operation, the only valid source is an Iterator of
+ * ParcelableKeyRing, each of which must contain either a single
+ * keyring encoded as bytes, or a unique reference to a keyring
+ * on keyservers and/or keybase.io.
+ * It is important to note that public keys should generally be imported before
+ * secret keys, because some implementations (notably Symantec PGP Desktop) do
+ * not include self certificates for user ids in the secret keyring. The import
+ * method here will generally import keyrings in the order given by the
+ * iterator, so this should be ensured beforehand.
+ *
+ * @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries()
+ */
+public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
+
+ public ImportOperation(Context context, ProviderHelper providerHelper, Progressable
+ progressable) {
+ super(context, providerHelper, progressable);
+ }
+
+ public ImportOperation(Context context, ProviderHelper providerHelper,
+ Progressable progressable, AtomicBoolean cancelled) {
+ super(context, providerHelper, progressable, cancelled);
+ }
+
+ // Overloaded functions for using progressable supplied in constructor during import
+ public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
+ String keyServerUri, Proxy proxy) {
+ return serialKeyRingImport(entries, num, keyServerUri, mProgressable, proxy);
+ }
+
+ @NonNull
+ private ImportKeyResult serialKeyRingImport(ParcelableFileCache<ParcelableKeyRing> cache,
+ 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, proxy);
+ } catch (IOException e) {
+
+ // Special treatment here, we need a lot
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_IMPORT, 0, 0);
+ log.add(LogType.MSG_IMPORT_ERROR_IO, 0, 0);
+
+ return new ImportKeyResult(ImportKeyResult.RESULT_ERROR, log);
+ }
+
+ }
+
+ /**
+ * Since the introduction of multithreaded import, we expect calling functions to handle the
+ * contact-to-key sync i.e ContactSyncAdapterService.requestSync()
+ *
+ * @param entries keys to import
+ * @param num number of keys to import
+ * @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
+ */
+ @NonNull
+ private ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
+ 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);
+
+ // If there aren't even any keys, do nothing here.
+ if (entries == null || !entries.hasNext()) {
+ return new ImportKeyResult(ImportKeyResult.RESULT_FAIL_NOTHING, log);
+ }
+
+ int newKeys = 0, updatedKeys = 0, badKeys = 0, secret = 0;
+ ArrayList<Long> importedMasterKeyIds = new ArrayList<>();
+
+ boolean cancelled = false;
+ int position = 0;
+ double progSteps = 100.0 / num;
+
+ KeybaseKeyserver keybaseServer = null;
+ HkpKeyserver keyServer = null;
+
+ // iterate over all entries
+ while (entries.hasNext()) {
+ ParcelableKeyRing entry = entries.next();
+
+ // Has this action been cancelled? If so, don't proceed any further
+ if (checkCancelled()) {
+ cancelled = true;
+ break;
+ }
+
+ try {
+
+ UncachedKeyRing key = null;
+
+ // If there is already byte data, use that
+ if (entry.mBytes != null) {
+ key = UncachedKeyRing.decodeFromData(entry.mBytes);
+ }
+ // Otherwise, we need to fetch the data from a server first
+ else {
+
+ // We fetch from keyservers first, because we tend to get more certificates
+ // from there, so the number of certificates which are merged in later is
+ // smaller.
+
+ // If we have a keyServerUri and a fingerprint or at least a keyId,
+ // download from HKP
+ if (keyServerUri != null
+ && (entry.mKeyIdHex != null || entry.mExpectedFingerprint != null)) {
+ // Make sure we have the keyserver instance cached
+ if (keyServer == null) {
+ log.add(LogType.MSG_IMPORT_KEYSERVER, 1, keyServerUri);
+ keyServer = new HkpKeyserver(keyServerUri);
+ }
+
+ try {
+ byte[] data;
+ // Download by fingerprint, or keyId - whichever is available
+ if (entry.mExpectedFingerprint != null) {
+ log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, "0x" +
+ entry.mExpectedFingerprint.substring(24));
+ data = keyServer.get("0x" + entry.mExpectedFingerprint, proxy)
+ .getBytes();
+ } else {
+ log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, entry.mKeyIdHex);
+ data = keyServer.get(entry.mKeyIdHex, proxy).getBytes();
+ }
+ key = UncachedKeyRing.decodeFromData(data);
+ if (key != null) {
+ log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_OK, 3);
+ } else {
+ log.add(LogType.MSG_IMPORT_FETCH_ERROR_DECODE, 3);
+ }
+ } catch (Keyserver.QueryFailedException e) {
+ Log.d(Constants.TAG, "query failed", e);
+ log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3, e.getMessage());
+ }
+ }
+
+ // If we have a keybase name, try to fetch from there
+ if (entry.mKeybaseName != null) {
+ // Make sure we have this cached
+ if (keybaseServer == null) {
+ keybaseServer = new KeybaseKeyserver();
+ }
+
+ try {
+ log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.mKeybaseName);
+ byte[] data = keybaseServer.get(entry.mKeybaseName, proxy).getBytes();
+ UncachedKeyRing keybaseKey = UncachedKeyRing.decodeFromData(data);
+
+ // If there already is a key, merge the two
+ if (key != null && keybaseKey != null) {
+ log.add(LogType.MSG_IMPORT_MERGE, 3);
+ keybaseKey = key.merge(keybaseKey, log, 4);
+ // If the merge didn't fail, use the new merged key
+ if (keybaseKey != null) {
+ key = keybaseKey;
+ } else {
+ log.add(LogType.MSG_IMPORT_MERGE_ERROR, 4);
+ }
+ } else if (keybaseKey != null) {
+ key = keybaseKey;
+ }
+ } catch (Keyserver.QueryFailedException e) {
+ // download failed, too bad. just proceed
+ Log.e(Constants.TAG, "query failed", e);
+ log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3, e.getMessage());
+ }
+ }
+ }
+
+ if (key == null) {
+ log.add(LogType.MSG_IMPORT_FETCH_ERROR, 2);
+ badKeys += 1;
+ continue;
+ }
+
+ // If we have an expected fingerprint, make sure it matches
+ if (entry.mExpectedFingerprint != null) {
+ if (!key.containsSubkey(entry.mExpectedFingerprint)) {
+ log.add(LogType.MSG_IMPORT_FINGERPRINT_ERROR, 2);
+ badKeys += 1;
+ continue;
+ } else {
+ log.add(LogType.MSG_IMPORT_FINGERPRINT_OK, 2);
+ }
+ }
+
+ // Another check if we have been cancelled
+ if (checkCancelled()) {
+ cancelled = true;
+ break;
+ }
+
+ SaveKeyringResult result;
+ // synchronizing prevents https://github.com/open-keychain/open-keychain/issues/1221
+ // and https://github.com/open-keychain/open-keychain/issues/1480
+ synchronized (mProviderHelper) {
+ mProviderHelper.clearLog();
+ if (key.isSecret()) {
+ result = mProviderHelper.saveSecretKeyRing(key,
+ new ProgressScaler(progressable, (int) (position * progSteps),
+ (int) ((position + 1) * progSteps), 100));
+ } else {
+ result = mProviderHelper.savePublicKeyRing(key,
+ new ProgressScaler(progressable, (int) (position * progSteps),
+ (int) ((position + 1) * progSteps), 100));
+ }
+ }
+ if (!result.success()) {
+ badKeys += 1;
+ } else {
+ if (result.updated()) {
+ updatedKeys += 1;
+ importedMasterKeyIds.add(key.getMasterKeyId());
+ } else {
+ newKeys += 1;
+ if (key.isSecret()) {
+ secret += 1;
+ }
+ importedMasterKeyIds.add(key.getMasterKeyId());
+ }
+ if (entry.mBytes == null) {
+ // synonymous to isDownloadFromKeyserver.
+ // If no byte data was supplied, import from keyserver took place
+ // this prevents file imports being noted as keyserver imports
+ mProviderHelper.renewKeyLastUpdatedTime(key.getMasterKeyId(),
+ GregorianCalendar.getInstance().getTimeInMillis(),
+ TimeUnit.MILLISECONDS);
+ }
+ }
+
+ log.add(result, 2);
+ } catch (IOException | PgpGeneralException e) {
+ Log.e(Constants.TAG, "Encountered bad key on import!", e);
+ ++badKeys;
+ }
+ // update progress
+ position++;
+ }
+
+ // Special: consolidate on secret key import (cannot be cancelled!)
+ // synchronized on mProviderHelper to prevent
+ // https://github.com/open-keychain/open-keychain/issues/1221 since a consolidate deletes
+ // and re-inserts keys, which could conflict with a parallel db key update
+ if (secret > 0) {
+ setPreventCancel();
+ ConsolidateResult result;
+ synchronized (mProviderHelper) {
+ result = mProviderHelper.consolidateDatabaseStep1(progressable);
+ }
+ log.add(result, 1);
+ }
+
+ // Special: make sure new data is synced into contacts
+ // disabling sync right now since it reduces speed while multi-threading
+ // so, we expect calling functions to take care of it. KeychainService handles this
+ // ContactSyncAdapterService.requestSync();
+
+ // convert to long array
+ long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()];
+ for (int i = 0; i < importedMasterKeyIds.size(); ++i) {
+ importedMasterKeyIdsArray[i] = importedMasterKeyIds.get(i);
+ }
+
+ int resultType = 0;
+ if (cancelled) {
+ log.add(LogType.MSG_OPERATION_CANCELLED, 1);
+ resultType |= ImportKeyResult.RESULT_CANCELLED;
+ }
+
+ // special return case: no new keys at all
+ if (badKeys == 0 && newKeys == 0 && updatedKeys == 0) {
+ resultType = ImportKeyResult.RESULT_FAIL_NOTHING;
+ } else {
+ if (newKeys > 0) {
+ resultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
+ }
+ if (updatedKeys > 0) {
+ resultType |= ImportKeyResult.RESULT_OK_UPDATED;
+ }
+ if (badKeys > 0) {
+ resultType |= ImportKeyResult.RESULT_WITH_ERRORS;
+ if (newKeys == 0 && updatedKeys == 0) {
+ resultType |= ImportKeyResult.RESULT_ERROR;
+ }
+ }
+ if (log.containsWarnings()) {
+ resultType |= ImportKeyResult.RESULT_WARNINGS;
+ }
+ }
+
+ // Final log entry, it's easier to do this individually
+ if ((newKeys > 0 || updatedKeys > 0) && badKeys > 0) {
+ log.add(LogType.MSG_IMPORT_PARTIAL, 1);
+ } else if (newKeys > 0 || updatedKeys > 0) {
+ log.add(LogType.MSG_IMPORT_SUCCESS, 1);
+ } else {
+ log.add(LogType.MSG_IMPORT_ERROR, 1);
+ }
+
+ return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
+ importedMasterKeyIdsArray);
+ }
+
+ @NonNull
+ @Override
+ public ImportKeyResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) {
+ ArrayList<ParcelableKeyRing> keyList = importInput.mKeyList;
+ String keyServer = importInput.mKeyserver;
+
+ ImportKeyResult result;
+
+ if (keyList == null) {// import from file, do serially
+ ParcelableFileCache<ParcelableKeyRing> cache = new ParcelableFileCache<>(mContext,
+ "key_import.pcl");
+
+ 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();
+ }
+
+ result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer, proxy);
+ }
+
+ ContactSyncAdapterService.requestSync();
+ return result;
+ }
+
+ @NonNull
+ private ImportKeyResult multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator,
+ 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);
+
+ final ProgressScaler ignoreProgressable = new ProgressScaler();
+
+ final int maxThreads = 200;
+ ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads,
+ 30L, TimeUnit.SECONDS,
+ new SynchronousQueue<Runnable>());
+
+ ExecutorCompletionService<ImportKeyResult> importCompletionService =
+ new ExecutorCompletionService<>(importExecutor);
+
+ while (keyListIterator.hasNext()) { // submit all key rings to be imported
+
+ final ParcelableKeyRing pkRing = keyListIterator.next();
+
+ Callable<ImportKeyResult> importOperationCallable = new Callable<ImportKeyResult>
+ () {
+
+ @Override
+ public ImportKeyResult call() {
+
+ ArrayList<ParcelableKeyRing> list = new ArrayList<>();
+ list.add(pkRing);
+
+ return serialKeyRingImport(list.iterator(), 1, keyServer,
+ ignoreProgressable, proxy);
+ }
+ };
+
+ importCompletionService.submit(importOperationCallable);
+ }
+
+ while (!accumulator.isImportFinished()) { // accumulate the results of each import
+ try {
+ accumulator.accumulateKeyImport(importCompletionService.take().get());
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(Constants.TAG, "A key could not be imported during multi-threaded " +
+ "import", e);
+ // do nothing?
+ if (e instanceof ExecutionException) {
+ // Since serialKeyRingImport does not throw any exceptions, this is what
+ // would have happened if
+ // we were importing the key on this thread
+ throw new RuntimeException();
+ }
+ }
+ }
+ return accumulator.getConsolidatedResult();
+ }
+ return new ImportKeyResult(ImportKeyResult.RESULT_FAIL_NOTHING, new OperationLog());
+ }
+
+ /**
+ * Used to accumulate the results of individual key imports
+ */
+ public static class KeyImportAccumulator {
+ private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
+ Progressable mProgressable;
+ private int mTotalKeys;
+ private int mImportedKeys = 0;
+ ArrayList<Long> mImportedMasterKeyIds = new ArrayList<>();
+ private int mBadKeys = 0;
+ private int mNewKeys = 0;
+ private int mUpdatedKeys = 0;
+ private int mSecret = 0;
+ private int mResultType = 0;
+
+ /**
+ * Accumulates keyring imports and updates the progressable whenever a new key is imported.
+ * Also sets the progress to 0 on instantiation.
+ *
+ * @param totalKeys total number of keys to be imported
+ * @param externalProgressable the external progressable to be updated every time a key
+ * is imported
+ */
+ public KeyImportAccumulator(int totalKeys, Progressable externalProgressable) {
+ mTotalKeys = totalKeys;
+ mProgressable = externalProgressable;
+ if (mProgressable != null) {
+ mProgressable.setProgress(0, totalKeys);
+ }
+ }
+
+ public synchronized void accumulateKeyImport(ImportKeyResult result) {
+ mImportedKeys++;
+
+ if (mProgressable != null) {
+ mProgressable.setProgress(mImportedKeys, mTotalKeys);
+ }
+
+ mImportLog.addAll(result.getLog().toList());//accumulates log
+ mBadKeys += result.mBadKeys;
+ mNewKeys += result.mNewKeys;
+ mUpdatedKeys += result.mUpdatedKeys;
+ mSecret += result.mSecret;
+
+ long[] masterKeyIds = result.getImportedMasterKeyIds();
+ for (long masterKeyId : masterKeyIds) {
+ mImportedMasterKeyIds.add(masterKeyId);
+ }
+
+ // if any key import has been cancelled, set result type to cancelled
+ // resultType is added to in getConsolidatedKayImport to account for remaining factors
+ mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
+ }
+
+ /**
+ * returns accumulated result of all imports so far
+ */
+ public ImportKeyResult getConsolidatedResult() {
+
+ // adding required information to mResultType
+ // special case,no keys requested for import
+ if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) {
+ mResultType = ImportKeyResult.RESULT_FAIL_NOTHING;
+ } else {
+ if (mNewKeys > 0) {
+ mResultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
+ }
+ if (mUpdatedKeys > 0) {
+ mResultType |= ImportKeyResult.RESULT_OK_UPDATED;
+ }
+ if (mBadKeys > 0) {
+ mResultType |= ImportKeyResult.RESULT_WITH_ERRORS;
+ if (mNewKeys == 0 && mUpdatedKeys == 0) {
+ mResultType |= ImportKeyResult.RESULT_ERROR;
+ }
+ }
+ if (mImportLog.containsWarnings()) {
+ mResultType |= ImportKeyResult.RESULT_WARNINGS;
+ }
+ }
+
+ long masterKeyIds[] = new long[mImportedMasterKeyIds.size()];
+ for (int i = 0; i < masterKeyIds.length; i++) {
+ masterKeyIds[i] = mImportedMasterKeyIds.get(i);
+ }
+
+ return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys,
+ mSecret, masterKeyIds);
+ }
+
+ public boolean isImportFinished() {
+ return mTotalKeys == mImportedKeys;
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java
new file mode 100644
index 000000000..8f1abde83
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 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 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;
+import de.measite.minidns.Client;
+import de.measite.minidns.DNSMessage;
+import de.measite.minidns.Question;
+import de.measite.minidns.Record;
+import de.measite.minidns.record.Data;
+import de.measite.minidns.record.TXT;
+import org.json.JSONObject;
+import org.spongycastle.openpgp.PGPUtil;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
+import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
+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 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> {
+
+ public KeybaseVerificationOperation(Context context, ProviderHelper providerHelper,
+ Progressable progressable) {
+ 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;
+
+ OperationResult.OperationLog log = new OperationResult.OperationLog();
+ log.add(OperationResult.LogType.MSG_KEYBASE_VERIFICATION, 0, requiredFingerprint);
+
+ try {
+ String keybaseProof = keybaseInput.mKeybaseProof;
+ Proof proof = new Proof(new JSONObject(keybaseProof));
+ mProgressable.setProgress(R.string.keybase_message_fetching_data, 0, 100);
+
+ Prover prover = Prover.findProverFor(proof);
+
+ if (prover == null) {
+ log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_NO_PROVER, 1,
+ proof.getPrettyName());
+ return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
+ }
+
+ if (!prover.fetchProofData(proxy)) {
+ log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_FETCH_PROOF, 1);
+ return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
+ }
+
+ if (!prover.checkFingerprint(requiredFingerprint)) {
+ log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_FINGERPRINT_MISMATCH, 1);
+ return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
+ }
+
+ String domain = prover.dnsTxtCheckRequired();
+ if (domain != null) {
+ DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT));
+ if (dnsQuery == null) {
+ log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_DNS_FAIL, 1);
+ log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 2,
+ getFlattenedProverLog(prover));
+ return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
+ }
+ Record[] records = dnsQuery.getAnswers();
+ List<List<byte[]>> extents = new ArrayList<>();
+ for (Record r : records) {
+ Data d = r.getPayload();
+ if (d instanceof TXT) {
+ extents.add(((TXT) d).getExtents());
+ }
+ }
+ if (!prover.checkDnsTxt(extents)) {
+ log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 1,
+ getFlattenedProverLog(prover));
+ return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
+ }
+ }
+
+ byte[] messageBytes = prover.getPgpMessage().getBytes();
+ if (prover.rawMessageCheckRequired()) {
+ InputStream messageByteStream = PGPUtil.getDecoderStream(new
+ ByteArrayInputStream
+ (messageBytes));
+ if (!prover.checkRawMessageBytes(messageByteStream)) {
+ log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 1,
+ getFlattenedProverLog(prover));
+ return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
+ }
+ }
+
+ PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable);
+
+ PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes)
+ .setSignedLiteralData(true)
+ .setRequiredSignerFingerprint(requiredFingerprint);
+
+ DecryptVerifyResult decryptVerifyResult = op.execute(input, new CryptoInputParcel());
+
+ if (!decryptVerifyResult.success()) {
+ log.add(decryptVerifyResult, 1);
+ return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
+ }
+
+ if (!prover.validate(new String(decryptVerifyResult.getOutputBytes()))) {
+ log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_PAYLOAD_MISMATCH, 1);
+ return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
+ }
+
+ return new KeybaseVerificationResult(OperationResult.RESULT_OK, log, prover);
+ } catch (Exception e) {
+ // just adds the passed parameter, in this case e.getMessage()
+ log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 1, e.getMessage());
+ return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
+ }
+ }
+
+ private String getFlattenedProverLog(Prover prover) {
+ String log = "";
+ for (String line : prover.getLog()) {
+ log += line + "\n";
+ }
+ return log;
+ }
+}
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 ef08b0b77..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;
@@ -25,17 +29,17 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
+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;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ProgressScaler;
-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
@@ -43,14 +47,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
* without secret key material, using a GNU_DUMMY s2k type.
*
*/
-public class PromoteKeyOperation extends BaseOperation {
+public class PromoteKeyOperation extends BaseOperation<PromoteKeyringParcel> {
public PromoteKeyOperation(Context context, ProviderHelper providerHelper,
Progressable progressable, AtomicBoolean cancelled) {
super(context, providerHelper, progressable, cancelled);
}
- public PromoteKeyResult execute(long masterKeyId, byte[] cardAid) {
+ @NonNull
+ @Override
+ public PromoteKeyResult execute(PromoteKeyringParcel promoteKeyringParcel,
+ CryptoInputParcel cryptoInputParcel) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_PR, 0);
@@ -61,12 +68,29 @@ public class PromoteKeyOperation extends BaseOperation {
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 (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(promoteKeyringParcel.mSubKeyIds, subKeyId) != null) {
+ log.add(LogType.MSG_PR_SUBKEY_MATCH, 1,
+ KeyFormattingUtils.convertKeyIdToHex(subKeyId));
+ } else {
+ log.add(LogType.MSG_PR_SUBKEY_NOMATCH, 1,
+ KeyFormattingUtils.convertKeyIdToHex(subKeyId));
+ }
+ }
+ }
// create divert-to-card secret key from public key
- promotedRing = pubRing.createDivertSecretRing(cardAid);
+ promotedRing = pubRing.createDivertSecretRing(promoteKeyringParcel.mCardAid,
+ promoteKeyringParcel.mSubKeyIds);
} catch (NotFoundException e) {
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
@@ -106,4 +130,13 @@ public class PromoteKeyOperation extends BaseOperation {
}
+ static private Integer naiveIndexOf(long[] haystack, long needle) {
+ for (int i = 0; i < haystack.length; i++) {
+ if (needle == haystack[i]) {
+ return i;
+ }
+ }
+ return null;
+ }
+
}
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..975cf541a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java
@@ -0,0 +1,107 @@
+/*
+ * 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.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 =
+ new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint());
+
+ // all revoke operations are made atomic as of now
+ saveKeyringParcel.setUpdateOptions(revokeKeyringParcel.mUpload, true,
+ revokeKeyringParcel.mKeyserver);
+
+ saveKeyringParcel.mRevokeSubKeys.add(masterKeyId);
+
+ 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_ERROR_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_ERROR_KEY_FAIL, 1);
+ return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
+ }
+ }
+
+}
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 651d15e8f..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;
@@ -55,13 +56,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
* a pending result, it will terminate.
*
*/
-public class SignEncryptOperation extends BaseOperation {
+public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
public SignEncryptOperation(Context context, ProviderHelper providerHelper,
Progressable progressable, AtomicBoolean cancelled) {
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 {
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 {
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 {
} 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 0a0e63330..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) {
@@ -132,7 +134,7 @@ public class CertifyResult extends InputPendingResult {
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, CertifyResult.this);
activity.startActivity(intent);
}
- }, R.string.view_log);
+ }, R.string.snackbar_details);
}
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 917b3415f..e8be9fa78 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* This program is free software: you can redistribute it and/or modify
@@ -20,19 +20,50 @@ package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
+import org.openintents.openpgp.OpenPgpDecryptionResult;
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 {
+ public static final int RESULT_NO_DATA = RESULT_ERROR + 16;
+ public static final int RESULT_KEY_DISALLOWED = RESULT_ERROR + 32;
+
OpenPgpSignatureResult mSignatureResult;
- OpenPgpMetadata mDecryptMetadata;
+ OpenPgpDecryptionResult mDecryptionResult;
+ OpenPgpMetadata mDecryptionMetadata;
// This holds the charset which was specified in the ascii armor, if specified
// https://tools.ietf.org/html/rfc4880#page56
String mCharset;
+ CryptoInputParcel mCachedCryptoInputParcel;
+
+ byte[] mOutputBytes;
+
+ public DecryptVerifyResult(int result, OperationLog log) {
+ super(result, log);
+ }
+
+ public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInput, cryptoInputParcel);
+ }
+
+ public DecryptVerifyResult(Parcel source) {
+ super(source);
+ mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
+ mDecryptionResult = source.readParcelable(OpenPgpDecryptionResult.class.getClassLoader());
+ mDecryptionMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
+ mCachedCryptoInputParcel = source.readParcelable(CryptoInputParcel.class.getClassLoader());
+ }
+
+
+ public boolean isKeysDisallowed () {
+ return (mResult & RESULT_KEY_DISALLOWED) == RESULT_KEY_DISALLOWED;
+ }
+
public OpenPgpSignatureResult getSignatureResult() {
return mSignatureResult;
}
@@ -41,38 +72,44 @@ public class DecryptVerifyResult extends InputPendingResult {
mSignatureResult = signatureResult;
}
- public OpenPgpMetadata getDecryptMetadata() {
- return mDecryptMetadata;
+ public OpenPgpDecryptionResult getDecryptionResult() {
+ return mDecryptionResult;
}
- public void setDecryptMetadata(OpenPgpMetadata decryptMetadata) {
- mDecryptMetadata = decryptMetadata;
+ public void setDecryptionResult(OpenPgpDecryptionResult decryptionResult) {
+ mDecryptionResult = decryptionResult;
}
- public String getCharset () {
- return mCharset;
+ public CryptoInputParcel getCachedCryptoInputParcel() {
+ return mCachedCryptoInputParcel;
}
- public void setCharset(String charset) {
- mCharset = charset;
+ public void setCachedCryptoInputParcel(CryptoInputParcel cachedCryptoInputParcel) {
+ mCachedCryptoInputParcel = cachedCryptoInputParcel;
}
- public boolean isPending() {
- return (mResult & RESULT_PENDING) == RESULT_PENDING;
+ public OpenPgpMetadata getDecryptionMetadata() {
+ return mDecryptionMetadata;
}
- public DecryptVerifyResult(int result, OperationLog log) {
- super(result, log);
+ public void setDecryptionMetadata(OpenPgpMetadata decryptMetadata) {
+ mDecryptionMetadata = decryptMetadata;
}
- public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput) {
- super(log, requiredInput);
+ public String getCharset () {
+ return mCharset;
}
- public DecryptVerifyResult(Parcel source) {
- super(source);
- mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
- mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
+ public void setCharset(String charset) {
+ mCharset = charset;
+ }
+
+ public void setOutputBytes(byte[] outputBytes) {
+ mOutputBytes = outputBytes;
+ }
+
+ public byte[] getOutputBytes() {
+ return mOutputBytes;
}
public int describeContents() {
@@ -81,8 +118,10 @@ public class DecryptVerifyResult extends InputPendingResult {
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeParcelable(mSignatureResult, 0);
- dest.writeParcelable(mDecryptMetadata, 0);
+ dest.writeParcelable(mSignatureResult, flags);
+ dest.writeParcelable(mDecryptionResult, flags);
+ dest.writeParcelable(mDecryptionMetadata, flags);
+ dest.writeParcelable(mCachedCryptoInputParcel, flags);
}
public static final Creator<DecryptVerifyResult> CREATOR = new Creator<DecryptVerifyResult>() {
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 50f49add2..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);
@@ -124,7 +143,7 @@ public class DeleteResult extends OperationResult {
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, DeleteResult.this);
activity.startActivity(intent);
}
- }, R.string.view_log);
+ }, R.string.snackbar_details);
}
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 1438ad698..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);
@@ -190,7 +203,7 @@ public class ImportKeyResult extends OperationResult {
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ImportKeyResult.this);
activity.startActivity(intent);
}
- }, R.string.view_log);
+ }, R.string.snackbar_details);
}
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
new file mode 100644
index 000000000..84648d32c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 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.os.Parcel;
+import android.os.Parcelable;
+import com.textuality.keybase.lib.KeybaseException;
+import com.textuality.keybase.lib.prover.Prover;
+
+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;
+
+ public KeybaseVerificationResult(int result, OperationLog log) {
+ super(result, log);
+ mProofUrl = null;
+ mPresenceLabel = null;
+ mPresenceUrl = null;
+ }
+
+ public KeybaseVerificationResult(int result, OperationLog log, Prover prover)
+ throws KeybaseException {
+ super(result, log);
+ mProofUrl = prover.getProofUrl();
+ mPresenceUrl = prover.getPresenceUrl();
+ 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();
+ mPresenceUrl = in.readString();
+ mPresenceLabel = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mProofUrl);
+ dest.writeString(mPresenceUrl);
+ dest.writeString(mPresenceLabel);
+ }
+
+ public static final Parcelable.Creator<KeybaseVerificationResult> CREATOR = new Parcelable.Creator<KeybaseVerificationResult>() {
+ @Override
+ public KeybaseVerificationResult createFromParcel(Parcel in) {
+ return new KeybaseVerificationResult(in);
+ }
+
+ @Override
+ public KeybaseVerificationResult[] newArray(int size) {
+ return new KeybaseVerificationResult[size];
+ }
+ };
+} \ No newline at end of file
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 c93db5c39..f213b1aad 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
@@ -22,6 +22,7 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -101,9 +102,9 @@ public abstract class OperationResult implements Parcelable {
}
public OperationLog getLog() {
- // If there is only a single entry, and it's a compound one, return that log
- if (mLog.isSingleCompound()) {
- return ((SubLogEntryParcel) mLog.getFirst()).getSubResult().getLog();
+ SubLogEntryParcel singleSubLog = mLog.getSubResultIfSingle();
+ if (singleSubLog != null) {
+ return singleSubLog.getSubResult().getLog();
}
// Otherwse, return our regular log
return mLog;
@@ -169,9 +170,9 @@ public abstract class OperationResult implements Parcelable {
public static class SubLogEntryParcel extends LogEntryParcel {
- OperationResult mSubResult;
+ @NonNull OperationResult mSubResult;
- public SubLogEntryParcel(OperationResult subResult, LogType type, int indent, Object... parameters) {
+ public SubLogEntryParcel(@NonNull OperationResult subResult, LogType type, int indent, Object... parameters) {
super(type, indent, parameters);
mSubResult = subResult;
@@ -209,6 +210,10 @@ public abstract class OperationResult implements Parcelable {
String logText;
LogEntryParcel entryParcel = mLog.getLast();
+ if (entryParcel == null) {
+ Log.e(Constants.TAG, "Tried to show empty log!");
+ return Notify.create(activity, R.string.error_empty_log, Style.ERROR);
+ }
// special case: first parameter may be a quantity
if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0
&& entryParcel.mParameters[0] instanceof Integer) {
@@ -248,7 +253,7 @@ public abstract class OperationResult implements Parcelable {
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
activity.startActivity(intent);
}
- }, R.string.view_log);
+ }, R.string.snackbar_details);
}
@@ -269,7 +274,7 @@ public abstract class OperationResult implements Parcelable {
* mark.
*
*/
- public static enum LogType {
+ public enum LogType {
MSG_INTERNAL_ERROR (LogLevel.ERROR, R.string.msg_internal_error),
MSG_OPERATION_CANCELLED (LogLevel.CANCELLED, R.string.msg_cancelled),
@@ -401,6 +406,7 @@ public abstract class OperationResult implements Parcelable {
MSG_KC_SUB_BAD_LOCAL(LogLevel.WARN, R.string.msg_kc_sub_bad_local),
MSG_KC_SUB_BAD_KEYID(LogLevel.WARN, R.string.msg_kc_sub_bad_keyid),
MSG_KC_SUB_BAD_TIME(LogLevel.WARN, R.string.msg_kc_sub_bad_time),
+ MSG_KC_SUB_BAD_TIME_EARLY(LogLevel.WARN, R.string.msg_kc_sub_bad_time_early),
MSG_KC_SUB_BAD_TYPE(LogLevel.WARN, R.string.msg_kc_sub_bad_type),
MSG_KC_SUB_DUP (LogLevel.DEBUG, R.string.msg_kc_sub_dup),
MSG_KC_SUB_PRIMARY_BAD(LogLevel.WARN, R.string.msg_kc_sub_primary_bad),
@@ -476,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),
@@ -493,11 +500,20 @@ 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),
+ MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD(LogLevel.ERROR, R.string.msg_mf_error_invalid_flags_for_keytocard),
+ MSG_MF_ERROR_BAD_NFC_ALGO(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_algo),
+ MSG_MF_ERROR_BAD_NFC_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_size),
+ MSG_MF_ERROR_BAD_NFC_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_stripped),
MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master),
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),
@@ -511,6 +527,8 @@ public abstract class OperationResult implements Parcelable {
MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new),
MSG_MF_SUBKEY_REVOKE (LogLevel.INFO, R.string.msg_mf_subkey_revoke),
MSG_MF_SUBKEY_STRIP (LogLevel.INFO, R.string.msg_mf_subkey_strip),
+ MSG_MF_KEYTOCARD_START (LogLevel.INFO, R.string.msg_mf_keytocard_start),
+ MSG_MF_KEYTOCARD_FINISH (LogLevel.OK, R.string.msg_mf_keytocard_finish),
MSG_MF_SUCCESS (LogLevel.OK, R.string.msg_mf_success),
MSG_MF_UID_ADD (LogLevel.INFO, R.string.msg_mf_uid_add),
MSG_MF_UID_PRIMARY (LogLevel.INFO, R.string.msg_mf_uid_primary),
@@ -553,13 +571,18 @@ 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),
// promote key
MSG_PR (LogLevel.START, R.string.msg_pr),
+ MSG_PR_ALL (LogLevel.DEBUG, R.string.msg_pr_all),
MSG_PR_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_pr_error_key_not_found),
MSG_PR_FETCHING (LogLevel.DEBUG, R.string.msg_pr_fetching),
+ MSG_PR_SUBKEY_MATCH (LogLevel.DEBUG, R.string.msg_pr_subkey_match),
+ MSG_PR_SUBKEY_NOMATCH (LogLevel.WARN, R.string.msg_pr_subkey_nomatch),
MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success),
// messages used in UI code
@@ -584,15 +607,16 @@ 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_SIGLIST(LogLevel.ERROR, R.string.msg_dc_error_invalid_siglist),
+ 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),
- MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO (LogLevel.ERROR, R.string.msg_dc_error_unsupported_hash_algo),
MSG_DC_INTEGRITY_CHECK_OK (LogLevel.INFO, R.string.msg_dc_integrity_check_ok),
MSG_DC_OK_META_ONLY (LogLevel.OK, R.string.msg_dc_ok_meta_only),
MSG_DC_OK (LogLevel.OK, R.string.msg_dc_ok),
@@ -607,7 +631,10 @@ public abstract class OperationResult implements Parcelable {
MSG_DC_TRAIL_SYM (LogLevel.DEBUG, R.string.msg_dc_trail_sym),
MSG_DC_TRAIL_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_trail_unknown),
MSG_DC_UNLOCKING (LogLevel.INFO, R.string.msg_dc_unlocking),
- MSG_DC_OLD_SYMMETRIC_ENCRYPTION_ALGO (LogLevel.WARN, R.string.msg_dc_old_symmetric_encryption_algo),
+ MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO(LogLevel.WARN, R.string.msg_dc_insecure_symmetric_encryption_algo),
+ MSG_DC_INSECURE_HASH_ALGO(LogLevel.ERROR, R.string.msg_dc_insecure_hash_algo),
+ MSG_DC_INSECURE_MDC_MISSING(LogLevel.ERROR, R.string.msg_dc_insecure_mdc_missing),
+ MSG_DC_INSECURE_KEY(LogLevel.ERROR, R.string.msg_dc_insecure_key),
// verify signed literal data
MSG_VL (LogLevel.INFO, R.string.msg_vl),
@@ -634,7 +661,6 @@ public abstract class OperationResult implements Parcelable {
MSG_PSE_COMPRESSING (LogLevel.DEBUG, R.string.msg_pse_compressing),
MSG_PSE_ENCRYPTING (LogLevel.DEBUG, R.string.msg_pse_encrypting),
MSG_PSE_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_pse_error_bad_passphrase),
- MSG_PSE_ERROR_HASH_ALGO (LogLevel.ERROR, R.string.msg_pse_error_hash_algo),
MSG_PSE_ERROR_IO (LogLevel.ERROR, R.string.msg_pse_error_io),
MSG_PSE_ERROR_SIGN_KEY(LogLevel.ERROR, R.string.msg_pse_error_sign_key),
MSG_PSE_ERROR_KEY_SIGN (LogLevel.ERROR, R.string.msg_pse_error_key_sign),
@@ -672,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),
@@ -683,6 +710,7 @@ public abstract class OperationResult implements Parcelable {
MSG_IMPORT_FETCH_KEYBASE (LogLevel.INFO, R.string.msg_import_fetch_keybase),
MSG_IMPORT_KEYSERVER (LogLevel.DEBUG, R.string.msg_import_keyserver),
MSG_IMPORT_MERGE (LogLevel.DEBUG, R.string.msg_import_merge),
+ MSG_IMPORT_MERGE_ERROR (LogLevel.ERROR, R.string.msg_import_merge_error),
MSG_IMPORT_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_import_fingerprint_error),
MSG_IMPORT_FINGERPRINT_OK (LogLevel.DEBUG, R.string.msg_import_fingerprint_ok),
MSG_IMPORT_ERROR (LogLevel.ERROR, R.string.msg_import_error),
@@ -691,6 +719,8 @@ 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_FILE_NAME (LogLevel.INFO, R.string.msg_export_file_name),
+ 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),
@@ -702,13 +732,16 @@ 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),
MSG_ACC_SAVED (LogLevel.INFO, R.string.api_settings_save_msg),
- MSG_WRONG_QR_CODE (LogLevel.INFO, R.string.import_qr_code_wrong),
+ MSG_WRONG_QR_CODE (LogLevel.ERROR, R.string.import_qr_code_wrong),
+ MSG_WRONG_QR_CODE_FP(LogLevel.ERROR, R.string.import_qr_code_fp),
MSG_NO_VALID_ENC (LogLevel.ERROR, R.string.error_invalid_data),
@@ -722,7 +755,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),
@@ -730,6 +763,25 @@ 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_NOT_FOUND (LogLevel.ERROR, R.string.msg_revoke_error_not_found),
+ MSG_REVOKE (LogLevel.DEBUG, R.string.msg_revoke_key),
+ MSG_REVOKE_ERROR_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),
+
+ MSG_KEYBASE_ERROR_NO_PROVER(LogLevel.ERROR, R.string.msg_keybase_error_no_prover),
+ MSG_KEYBASE_ERROR_FETCH_PROOF(LogLevel.ERROR, R.string.msg_keybase_error_fetching_evidence),
+ MSG_KEYBASE_ERROR_FINGERPRINT_MISMATCH(LogLevel.ERROR,
+ R.string.msg_keybase_error_key_mismatch),
+ MSG_KEYBASE_ERROR_DNS_FAIL(LogLevel.ERROR, R.string.msg_keybase_error_dns_fail),
+ MSG_KEYBASE_ERROR_SPECIFIC(LogLevel.ERROR, R.string.msg_keybase_error_specific),
+ MSG_KEYBASE_ERROR_PAYLOAD_MISMATCH(LogLevel.ERROR,
+ R.string.msg_keybase_error_msg_payload_mismatch),
+
+ // export log
MSG_LV (LogLevel.START, R.string.msg_lv),
MSG_LV_MATCH (LogLevel.DEBUG, R.string.msg_lv_match),
MSG_LV_MATCH_ERROR (LogLevel.ERROR, R.string.msg_lv_match_error),
@@ -770,7 +822,7 @@ public abstract class OperationResult implements Parcelable {
}
/** Enumeration of possible log levels. */
- public static enum LogLevel {
+ public enum LogLevel {
DEBUG,
INFO,
WARN,
@@ -810,8 +862,15 @@ public abstract class OperationResult implements Parcelable {
mParcels.add(new SubLogEntryParcel(subResult, subLog.getFirst().mType, indent, subLog.getFirst().mParameters));
}
- boolean isSingleCompound() {
- return mParcels.size() == 1 && getFirst() instanceof SubLogEntryParcel;
+ public SubLogEntryParcel getSubResultIfSingle() {
+ if (mParcels.size() != 1) {
+ return null;
+ }
+ LogEntryParcel first = getFirst();
+ if (first instanceof SubLogEntryParcel) {
+ return (SubLogEntryParcel) first;
+ }
+ return null;
}
public void clear() {
@@ -859,7 +918,11 @@ public abstract class OperationResult implements Parcelable {
if (mParcels.isEmpty()) {
return null;
}
- return mParcels.get(mParcels.size() -1);
+ LogEntryParcel last = mParcels.get(mParcels.size() -1);
+ if (last instanceof SubLogEntryParcel) {
+ return ((SubLogEntryParcel) last).getSubResult().getLog().getLast();
+ }
+ return last;
}
@Override
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 4adacaf23..770e8de91 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
@@ -19,6 +19,7 @@
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPPublicKey;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -26,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.
*
@@ -90,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()) {
@@ -127,7 +141,11 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
}
public CanonicalizedPublicKey getPublicKey(long id) {
- return new CanonicalizedPublicKey(this, getRing().getPublicKey(id));
+ PGPPublicKey pubKey = getRing().getPublicKey(id);
+ if (pubKey == null) {
+ return null;
+ }
+ return new CanonicalizedPublicKey(this, pubKey);
}
public byte[] getEncoded() throws IOException {
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 8432b8f9f..be5f21f23 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
@@ -18,10 +18,10 @@
package org.sufficientlysecure.keychain.pgp;
-import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -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();
@@ -97,15 +84,25 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
}
/** Create a dummy secret ring from this key */
- public UncachedKeyRing createDummySecretRing () {
- PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), null);
- return new UncachedKeyRing(secRing);
- }
-
- /** Create a dummy secret ring from this key */
- public UncachedKeyRing createDivertSecretRing (byte[] cardAid) {
+ public UncachedKeyRing createDivertSecretRing (byte[] cardAid, long[] subKeyIds) {
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid);
- return new UncachedKeyRing(secRing);
+
+ if (subKeyIds == null) {
+ return new UncachedKeyRing(secRing);
+ }
+
+ // if only specific subkeys should be promoted, construct a
+ // stripped dummy, then move divert-to-card keys over
+ PGPSecretKeyRing newRing = PGPSecretKeyRing.constructDummyFromPublic(getRing());
+ for (long subKeyId : subKeyIds) {
+ PGPSecretKey key = secRing.getSecretKey(subKeyId);
+ if (key != null) {
+ newRing = PGPSecretKeyRing.insertSecretKey(newRing, key);
+ }
+ }
+
+ return new UncachedKeyRing(newRing);
+
}
} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
index 39d0a2f1d..7394c07c3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
@@ -21,22 +21,18 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
-import org.spongycastle.openpgp.PGPPublicKey;
-import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
-import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
-import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
-import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
-import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFactoryBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
@@ -45,10 +41,10 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.nio.ByteBuffer;
-import java.util.ArrayList;
+import java.security.PrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
import java.util.Date;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
@@ -69,9 +65,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
private PGPPrivateKey mPrivateKey = null;
private int mPrivateKeyState = PRIVATE_KEY_STATE_LOCKED;
- private static int PRIVATE_KEY_STATE_LOCKED = 0;
- private static int PRIVATE_KEY_STATE_UNLOCKED = 1;
- private static int PRIVATE_KEY_STATE_DIVERT_TO_CARD = 2;
+ final private static int PRIVATE_KEY_STATE_LOCKED = 0;
+ final private static int PRIVATE_KEY_STATE_UNLOCKED = 1;
+ final private static int PRIVATE_KEY_STATE_DIVERT_TO_CARD = 2;
CanonicalizedSecretKey(CanonicalizedSecretKeyRing ring, PGPSecretKey key) {
super(ring, key.getPublicKey());
@@ -123,9 +119,10 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
}
public SecretKeyType getSecretKeyType() {
- if (mSecretKey.getS2K() != null && mSecretKey.getS2K().getType() == S2K.GNU_DUMMY_S2K) {
+ S2K s2k = mSecretKey.getS2K();
+ if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K) {
// divert to card is special
- if (mSecretKey.getS2K().getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
+ if (s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
return SecretKeyType.DIVERT_TO_CARD;
}
// no matter the exact protection mode, it's some kind of dummy key
@@ -156,9 +153,10 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
*/
public boolean unlock(Passphrase passphrase) throws PgpGeneralException {
// handle keys on OpenPGP cards like they were unlocked
- if (mSecretKey.getS2K() != null
- && mSecretKey.getS2K().getType() == S2K.GNU_DUMMY_S2K
- && mSecretKey.getS2K().getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
+ S2K s2k = mSecretKey.getS2K();
+ if (s2k != null
+ && s2k.getType() == S2K.GNU_DUMMY_S2K
+ && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
mPrivateKeyState = PRIVATE_KEY_STATE_DIVERT_TO_CARD;
return true;
}
@@ -178,16 +176,6 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
return true;
}
- /**
- * Returns a list of all supported hash algorithms.
- */
- public ArrayList<Integer> getSupportedHashAlgorithms() {
- // TODO: intersection between preferred hash algos of this key and PgpConstants.PREFERRED_HASH_ALGORITHMS
- // choose best algo
-
- return PgpConstants.sPreferredHashAlgorithms;
- }
-
private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo,
Map<ByteBuffer,byte[]> signedHashes) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
@@ -206,7 +194,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
public PGPSignatureGenerator getCertSignatureGenerator(Map<ByteBuffer, byte[]> signedHashes) {
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
- PgpConstants.CERTIFY_HASH_ALGO, signedHashes);
+ PgpSecurityConstants.CERTIFY_HASH_ALGO, signedHashes);
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
@@ -265,20 +253,42 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
}
}
- public PublicKeyDataDecryptorFactory getDecryptorFactory(CryptoInputParcel cryptoInput) {
+ public CachingDataDecryptorFactory getCachingDecryptorFactory(CryptoInputParcel cryptoInput) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
- return new NfcSyncPublicKeyDataDecryptorFactoryBuilder()
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
- cryptoInput.getCryptoData()
- );
+ return new CachingDataDecryptorFactory(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME,
+ cryptoInput.getCryptoData());
} else {
- return new JcePublicKeyDataDecryptorFactoryBuilder()
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey);
+ return new CachingDataDecryptorFactory(
+ new JcePublicKeyDataDecryptorFactoryBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey),
+ cryptoInput.getCryptoData());
+ }
+ }
+
+ // For use only in card export; returns the secret key in Chinese Remainder Theorem format.
+ public RSAPrivateCrtKey getCrtSecretKey() throws PgpGeneralException {
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
+ throw new PgpGeneralException("Cannot get secret key attributes while key is locked.");
}
+
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
+ throw new PgpGeneralException("Cannot get secret key attributes of divert-to-card key.");
+ }
+
+ JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
+ PrivateKey retVal;
+ try {
+ retVal = keyConverter.getPrivateKey(mPrivateKey);
+ } catch (PGPException e) {
+ throw new PgpGeneralException("Error converting private key!", e);
+ }
+
+ return (RSAPrivateCrtKey)retVal;
}
public byte[] getIv() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java
index 825795cc6..77977b691 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java
@@ -22,6 +22,7 @@ import android.text.TextUtils;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -91,7 +92,7 @@ public abstract class KeyRing {
return userIdString;
}
- public static class UserId {
+ public static class UserId implements Serializable {
public final String name;
public final String email;
public final String comment;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java
new file mode 100644
index 000000000..c4525e5cd
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java
@@ -0,0 +1,59 @@
+/*
+ * 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.pgp;
+
+import org.openintents.openpgp.OpenPgpDecryptionResult;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class OpenPgpDecryptionResultBuilder {
+
+ // builder
+ private boolean mInsecure = false;
+ private boolean mEncrypted = false;
+
+ public void setInsecure(boolean insecure) {
+ this.mInsecure = insecure;
+ }
+
+ public void setEncrypted(boolean encrypted) {
+ this.mEncrypted = encrypted;
+ }
+
+ public OpenPgpDecryptionResult build() {
+ OpenPgpDecryptionResult result = new OpenPgpDecryptionResult();
+
+ if (mInsecure) {
+ Log.d(Constants.TAG, "RESULT_INSECURE");
+ result.setResult(OpenPgpDecryptionResult.RESULT_INSECURE);
+ return result;
+ }
+
+ if (mEncrypted) {
+ Log.d(Constants.TAG, "RESULT_ENCRYPTED");
+ result.setResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED);
+ } else {
+ Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED");
+ result.setResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED);
+ }
+
+ return result;
+ }
+
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
index ed4715681..9d059b58f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
@@ -30,7 +30,6 @@ import java.util.ArrayList;
*/
public class OpenPgpSignatureResultBuilder {
// OpenPgpSignatureResult
- private boolean mSignatureOnly = false;
private String mPrimaryUserId;
private ArrayList<String> mUserIds = new ArrayList<>();
private long mKeyId;
@@ -42,10 +41,7 @@ public class OpenPgpSignatureResultBuilder {
private boolean mIsSignatureKeyCertified = false;
private boolean mIsKeyRevoked = false;
private boolean mIsKeyExpired = false;
-
- public void setSignatureOnly(boolean signatureOnly) {
- this.mSignatureOnly = signatureOnly;
- }
+ private boolean mInsecure = false;
public void setPrimaryUserId(String userId) {
this.mPrimaryUserId = userId;
@@ -63,6 +59,10 @@ public class OpenPgpSignatureResultBuilder {
this.mValidSignature = validSignature;
}
+ public void setInsecure(boolean insecure) {
+ this.mInsecure = insecure;
+ }
+
public void setSignatureKeyCertified(boolean isSignatureKeyCertified) {
this.mIsSignatureKeyCertified = isSignatureKeyCertified;
}
@@ -87,6 +87,10 @@ public class OpenPgpSignatureResultBuilder {
return mValidSignature;
}
+ public boolean isInsecure() {
+ return mInsecure;
+ }
+
public void initValid(CanonicalizedPublicKeyRing signingRing,
CanonicalizedPublicKey signingKey) {
setSignatureAvailable(true);
@@ -109,47 +113,50 @@ public class OpenPgpSignatureResultBuilder {
}
public OpenPgpSignatureResult build() {
- if (mSignatureAvailable) {
- OpenPgpSignatureResult result = new OpenPgpSignatureResult();
- result.setSignatureOnly(mSignatureOnly);
-
- // valid sig!
- if (mKnownKey) {
- if (mValidSignature) {
- result.setKeyId(mKeyId);
- result.setPrimaryUserId(mPrimaryUserId);
- result.setUserIds(mUserIds);
-
- if (mIsKeyRevoked) {
- Log.d(Constants.TAG, "SIGNATURE_KEY_REVOKED");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED);
- } else if (mIsKeyExpired) {
- Log.d(Constants.TAG, "SIGNATURE_KEY_EXPIRED");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED);
- } else if (mIsSignatureKeyCertified) {
- Log.d(Constants.TAG, "SIGNATURE_SUCCESS_CERTIFIED");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED);
- } else {
- Log.d(Constants.TAG, "SIGNATURE_SUCCESS_UNCERTIFIED");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED);
- }
- } else {
- Log.d(Constants.TAG, "Error! Invalid signature.");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_ERROR);
- }
- } else {
- result.setKeyId(mKeyId);
-
- Log.d(Constants.TAG, "SIGNATURE_KEY_MISSING");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_KEY_MISSING);
- }
+ OpenPgpSignatureResult result = new OpenPgpSignatureResult();
+ if (!mSignatureAvailable) {
+ Log.d(Constants.TAG, "RESULT_NO_SIGNATURE");
+ result.setResult(OpenPgpSignatureResult.RESULT_NO_SIGNATURE);
return result;
- } else {
- Log.d(Constants.TAG, "no signature found!");
+ }
+
+ if (!mKnownKey) {
+ result.setKeyId(mKeyId);
- return null;
+ Log.d(Constants.TAG, "RESULT_KEY_MISSING");
+ result.setResult(OpenPgpSignatureResult.RESULT_KEY_MISSING);
+ return result;
+ }
+
+ if (!mValidSignature) {
+ Log.d(Constants.TAG, "RESULT_INVALID_SIGNATURE");
+ result.setResult(OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE);
+ return result;
}
+
+ result.setKeyId(mKeyId);
+ result.setPrimaryUserId(mPrimaryUserId);
+ result.setUserIds(mUserIds);
+
+ if (mIsKeyRevoked) {
+ Log.d(Constants.TAG, "RESULT_INVALID_KEY_REVOKED");
+ result.setResult(OpenPgpSignatureResult.RESULT_INVALID_KEY_REVOKED);
+ } else if (mIsKeyExpired) {
+ Log.d(Constants.TAG, "RESULT_INVALID_KEY_EXPIRED");
+ result.setResult(OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED);
+ } else if (mInsecure) {
+ Log.d(Constants.TAG, "RESULT_INVALID_INSECURE");
+ result.setResult(OpenPgpSignatureResult.RESULT_INVALID_INSECURE);
+ } else if (mIsSignatureKeyCertified) {
+ Log.d(Constants.TAG, "RESULT_VALID_CONFIRMED");
+ result.setResult(OpenPgpSignatureResult.RESULT_VALID_CONFIRMED);
+ } else {
+ Log.d(Constants.TAG, "RESULT_VALID_UNCONFIRMED");
+ result.setResult(OpenPgpSignatureResult.RESULT_VALID_UNCONFIRMED);
+ }
+
+ return result;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConstants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConstants.java
deleted file mode 100644
index f739b1e6d..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConstants.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.pgp;
-
-import org.spongycastle.bcpg.CompressionAlgorithmTags;
-import org.spongycastle.bcpg.HashAlgorithmTags;
-import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
-
-import java.util.ArrayList;
-
-public class PgpConstants {
-
- public static ArrayList<Integer> sPreferredSymmetricAlgorithms = new ArrayList<>();
- public static ArrayList<Integer> sPreferredHashAlgorithms = new ArrayList<>();
- public static ArrayList<Integer> sPreferredCompressionAlgorithms = new ArrayList<>();
-
- // TODO: use hashmaps for contains in O(1) and intersections!
-
- /*
- * Most preferred is first
- * These arrays are written as preferred algorithms into the keys on creation.
- * Other implementations may choose to honor this selection.
- *
- * These lists also define the only algorithms which are used in OpenKeychain.
- * We do not support algorithms such as MD5
- */
- static {
- sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.AES_256);
- sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.AES_192);
- sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.AES_128);
- sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.TWOFISH);
-
- // NOTE: some implementations do not support SHA512, thus we choose SHA256 as default (Mailvelope?)
- sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA256);
- sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA512);
- sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA384);
- sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA224);
- sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA1);
- sPreferredHashAlgorithms.add(HashAlgorithmTags.RIPEMD160);
-
- /*
- * Prefer ZIP
- * "ZLIB provides no benefit over ZIP and is more malleable"
- * - (OpenPGP WG mailinglist: "[openpgp] Intent to deprecate: Insecure primitives")
- * BZIP2: very slow
- */
- sPreferredCompressionAlgorithms.add(CompressionAlgorithmTags.ZIP);
- sPreferredCompressionAlgorithms.add(CompressionAlgorithmTags.ZLIB);
- sPreferredCompressionAlgorithms.add(CompressionAlgorithmTags.BZIP2);
- }
-
- public static final int CERTIFY_HASH_ALGO = HashAlgorithmTags.SHA256;
-
- /*
- * Note: s2kcount is a number between 0 and 0xff that controls the
- * number of times to iterate the password hash before use. More
- * iterations are useful against offline attacks, as it takes more
- * time to check each password. The actual number of iterations is
- * rather complex, and also depends on the hash function in use.
- * Refer to Section 3.7.1.3 in rfc4880.txt. Bigger numbers give
- * you more iterations. As a rough rule of thumb, when using
- * SHA256 as the hashing function, 0x10 gives you about 64
- * iterations, 0x20 about 128, 0x30 about 256 and so on till 0xf0,
- * or about 1 million iterations. The maximum you can go to is
- * 0xff, or about 2 million iterations.
- * from http://kbsriram.com/2013/01/generating-rsa-keys-with-bouncycastle.html
- *
- * Bouncy Castle default: 0x60
- * kbsriram proposes: 0xc0
- * OpenKeychain: 0x90
- */
- public static final int SECRET_KEY_ENCRYPTOR_S2K_COUNT = 0x90;
- public static final int SECRET_KEY_ENCRYPTOR_HASH_ALGO = HashAlgorithmTags.SHA256;
- public static final int SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO = SymmetricKeyAlgorithmTags.AES_256;
- public static final int SECRET_KEY_SIGNATURE_HASH_ALGO = HashAlgorithmTags.SHA256;
- // NOTE: only SHA1 is supported for key checksum calculations in OpenPGP,
- // see http://tools.ietf.org/html/rfc488 0#section-5.5.3
- public static final int SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO = HashAlgorithmTags.SHA1;
-
- public static interface OpenKeychainSymmetricKeyAlgorithmTags extends SymmetricKeyAlgorithmTags {
- public static final int USE_PREFERRED = -1;
- }
-
- public static interface OpenKeychainHashAlgorithmTags extends HashAlgorithmTags {
- public static final int USE_PREFERRED = -1;
- }
-
- public static interface OpenKeychainCompressionAlgorithmTags extends CompressionAlgorithmTags {
- public static final int USE_PREFERRED = -1;
- }
-
- public static int[] getAsArray(ArrayList<Integer> list) {
- int[] array = new int[list.size()];
- for (int i = 0; i < list.size(); i++) {
- array[i] = list.get(i);
- }
- return array;
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
new file mode 100644
index 000000000..a6d65688c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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.pgp;
+
+import java.util.HashSet;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class PgpDecryptVerifyInputParcel implements Parcelable {
+
+ private Uri mInputUri;
+ private Uri mOutputUri;
+ private byte[] mInputBytes;
+
+ private boolean mAllowSymmetricDecryption;
+ private HashSet<Long> mAllowedKeyIds;
+ private boolean mDecryptMetadataOnly;
+ private byte[] mDetachedSignature;
+ private String mRequiredSignerFingerprint;
+ private boolean mSignedLiteralData;
+
+ public PgpDecryptVerifyInputParcel() {
+ }
+
+ public PgpDecryptVerifyInputParcel(Uri inputUri, Uri outputUri) {
+ mInputUri = inputUri;
+ mOutputUri = outputUri;
+ }
+
+ public PgpDecryptVerifyInputParcel(byte[] inputBytes) {
+ mInputBytes = inputBytes;
+ }
+
+ PgpDecryptVerifyInputParcel(Parcel source) {
+ // we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
+ mInputUri = source.readParcelable(getClass().getClassLoader());
+ mOutputUri = source.readParcelable(getClass().getClassLoader());
+ mInputBytes = source.createByteArray();
+
+ mAllowSymmetricDecryption = source.readInt() != 0;
+ mAllowedKeyIds = (HashSet<Long>) source.readSerializable();
+ mDecryptMetadataOnly = source.readInt() != 0;
+ mDetachedSignature = source.createByteArray();
+ mRequiredSignerFingerprint = source.readString();
+ mSignedLiteralData = source.readInt() != 0;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mInputUri, 0);
+ dest.writeParcelable(mOutputUri, 0);
+ dest.writeByteArray(mInputBytes);
+
+ dest.writeInt(mAllowSymmetricDecryption ? 1 : 0);
+ dest.writeSerializable(mAllowedKeyIds);
+ dest.writeInt(mDecryptMetadataOnly ? 1 : 0);
+ dest.writeByteArray(mDetachedSignature);
+ dest.writeString(mRequiredSignerFingerprint);
+ dest.writeInt(mSignedLiteralData ? 1 : 0);
+ }
+
+ byte[] getInputBytes() {
+ return mInputBytes;
+ }
+
+ Uri getInputUri() {
+ return mInputUri;
+ }
+
+ Uri getOutputUri() {
+ return mOutputUri;
+ }
+
+ boolean isAllowSymmetricDecryption() {
+ return mAllowSymmetricDecryption;
+ }
+
+ public PgpDecryptVerifyInputParcel setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
+ mAllowSymmetricDecryption = allowSymmetricDecryption;
+ return this;
+ }
+
+ HashSet<Long> getAllowedKeyIds() {
+ return mAllowedKeyIds;
+ }
+
+ public PgpDecryptVerifyInputParcel setAllowedKeyIds(HashSet<Long> allowedKeyIds) {
+ mAllowedKeyIds = allowedKeyIds;
+ return this;
+ }
+
+ boolean isDecryptMetadataOnly() {
+ return mDecryptMetadataOnly;
+ }
+
+ public PgpDecryptVerifyInputParcel setDecryptMetadataOnly(boolean decryptMetadataOnly) {
+ mDecryptMetadataOnly = decryptMetadataOnly;
+ return this;
+ }
+
+ byte[] getDetachedSignature() {
+ return mDetachedSignature;
+ }
+
+ public PgpDecryptVerifyInputParcel setDetachedSignature(byte[] detachedSignature) {
+ mDetachedSignature = detachedSignature;
+ return this;
+ }
+
+ String getRequiredSignerFingerprint() {
+ return mRequiredSignerFingerprint;
+ }
+
+ public PgpDecryptVerifyInputParcel setRequiredSignerFingerprint(String requiredSignerFingerprint) {
+ mRequiredSignerFingerprint = requiredSignerFingerprint;
+ return this;
+ }
+
+ boolean isSignedLiteralData() {
+ return mSignedLiteralData;
+ }
+
+ public PgpDecryptVerifyInputParcel setSignedLiteralData(boolean signedLiteralData) {
+ mSignedLiteralData = signedLiteralData;
+ return this;
+ }
+
+ public static final Creator<PgpDecryptVerifyInputParcel> CREATOR = new Creator<PgpDecryptVerifyInputParcel>() {
+ public PgpDecryptVerifyInputParcel createFromParcel(final Parcel source) {
+ return new PgpDecryptVerifyInputParcel(source);
+ }
+
+ public PgpDecryptVerifyInputParcel[] newArray(final int size) {
+ return new PgpDecryptVerifyInputParcel[size];
+ }
+ };
+
+}
+
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
index f6580b85a..dd30156f9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
@@ -19,15 +19,19 @@
package org.sufficientlysecure.keychain.pgp;
import android.content.Context;
+import android.support.annotation.NonNull;
import android.webkit.MimeTypeMap;
+import org.openintents.openpgp.OpenPgpDecryptionResult;
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;
+import org.spongycastle.openpgp.PGPKeyValidationException;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPOnePassSignature;
import org.spongycastle.openpgp.PGPOnePassSignatureList;
@@ -39,12 +43,13 @@ import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
-import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+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.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFactoryBuilder;
+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;
@@ -57,6 +62,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
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.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
@@ -65,154 +71,99 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.URLConnection;
import java.security.SignatureException;
import java.util.Date;
import java.util.Iterator;
-import java.util.Set;
-/**
- * This class uses a Builder pattern!
- */
-public class PgpDecryptVerify extends BaseOperation {
-
- private InputData mData;
- private OutputStream mOutStream;
-
- private boolean mAllowSymmetricDecryption;
- private Set<Long> mAllowedKeyIds;
- private boolean mDecryptMetadataOnly;
- private byte[] mDetachedSignature;
- private String mRequiredSignerFingerprint;
- private boolean mSignedLiteralData;
-
- protected PgpDecryptVerify(Builder builder) {
- super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
-
- // private Constructor can only be called from Builder
- this.mData = builder.mData;
- this.mOutStream = builder.mOutStream;
-
- this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
- this.mAllowedKeyIds = builder.mAllowedKeyIds;
- this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
- this.mDetachedSignature = builder.mDetachedSignature;
- this.mSignedLiteralData = builder.mSignedLiteralData;
- this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
- }
+public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInputParcel> {
- public static class Builder {
- // mandatory parameter
- private Context mContext;
- private ProviderHelper mProviderHelper;
- private InputData mData;
-
- // optional
- private OutputStream mOutStream = null;
- private Progressable mProgressable = null;
- private boolean mAllowSymmetricDecryption = true;
- private Set<Long> mAllowedKeyIds = null;
- private boolean mDecryptMetadataOnly = false;
- private byte[] mDetachedSignature = null;
- private String mRequiredSignerFingerprint = null;
- private boolean mSignedLiteralData = false;
-
- public Builder(Context context, ProviderHelper providerHelper,
- Progressable progressable,
- InputData data, OutputStream outStream) {
- mContext = context;
- mProviderHelper = providerHelper;
- mProgressable = progressable;
- mData = data;
- mOutStream = outStream;
- }
+ public PgpDecryptVerifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
+ super(context, providerHelper, progressable);
+ }
- /**
- * This is used when verifying signed literals to check that they are signed with
- * the required key
- */
- public Builder setRequiredSignerFingerprint(String fingerprint) {
- mRequiredSignerFingerprint = fingerprint;
- return this;
- }
+ /** Decrypts and/or verifies data based on parameters of PgpDecryptVerifyInputParcel. */
+ @NonNull
+ public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput) {
+ InputData inputData;
+ OutputStream outputStream;
- /**
- * This is to force a mode where the message is just the signature key id and
- * then a literal data packet; used in Keybase.io proofs
- */
- public Builder setSignedLiteralData(boolean signedLiteralData) {
- mSignedLiteralData = signedLiteralData;
- return this;
+ if (input.getInputBytes() != null) {
+ byte[] inputBytes = input.getInputBytes();
+ inputData = new InputData(new ByteArrayInputStream(inputBytes), inputBytes.length);
+ } else {
+ try {
+ InputStream inputStream = mContext.getContentResolver().openInputStream(input.getInputUri());
+ long inputSize = FileHelper.getFileSize(mContext, input.getInputUri(), 0);
+ inputData = new InputData(inputStream, inputSize);
+ } catch (FileNotFoundException e) {
+ 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);
+ }
}
- public Builder setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
- mAllowSymmetricDecryption = allowSymmetricDecryption;
- return this;
+ if (input.getOutputUri() == null) {
+ outputStream = new ByteArrayOutputStream();
+ } else {
+ try {
+ outputStream = mContext.getContentResolver().openOutputStream(input.getOutputUri());
+ } catch (FileNotFoundException e) {
+ 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);
+ }
}
- /**
- * Allow these key ids alone for decryption.
- * This means only ciphertexts encrypted for one of these private key can be decrypted.
- */
- public Builder setAllowedKeyIds(Set<Long> allowedKeyIds) {
- mAllowedKeyIds = allowedKeyIds;
- return this;
+ DecryptVerifyResult result = executeInternal(input, cryptoInput, inputData, outputStream);
+ if (outputStream instanceof ByteArrayOutputStream) {
+ byte[] outputData = ((ByteArrayOutputStream) outputStream).toByteArray();
+ result.setOutputBytes(outputData);
}
- /**
- * If enabled, the actual decryption/verification of the content will not be executed.
- * The metadata only will be decrypted and returned.
- */
- public Builder setDecryptMetadataOnly(boolean decryptMetadataOnly) {
- mDecryptMetadataOnly = decryptMetadataOnly;
- return this;
- }
+ return result;
- /**
- * If detachedSignature != null, it will be used exclusively to verify the signature
- */
- public Builder setDetachedSignature(byte[] detachedSignature) {
- mDetachedSignature = detachedSignature;
- return this;
- }
+ }
- public PgpDecryptVerify build() {
- return new PgpDecryptVerify(this);
- }
+ @NonNull
+ public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
+ InputData inputData, OutputStream outputStream) {
+ return executeInternal(input, cryptoInput, inputData, outputStream);
}
- /**
- * Decrypts and/or verifies data based on parameters of class
- */
- public DecryptVerifyResult execute(CryptoInputParcel cryptoInput) {
+ @NonNull
+ private DecryptVerifyResult executeInternal(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
+ InputData inputData, OutputStream outputStream) {
try {
- if (mDetachedSignature != null) {
+ if (input.getDetachedSignature() != null) {
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only");
- return verifyDetachedSignature(mData.getInputStream(), 0);
+ return verifyDetachedSignature(input, inputData, outputStream, 0);
} else {
// automatically works with PGP ascii armor and PGP binary
- InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
+ InputStream in = PGPUtil.getDecoderStream(inputData.getInputStream());
if (in instanceof ArmoredInputStream) {
ArmoredInputStream aIn = (ArmoredInputStream) in;
// it is ascii armored
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
- if (mSignedLiteralData) {
- return verifySignedLiteralData(aIn, 0);
+ if (input.isSignedLiteralData()) {
+ return verifySignedLiteralData(input, aIn, outputStream, 0);
} else if (aIn.isClearText()) {
// a cleartext signature, verify it with the other method
- return verifyCleartextSignature(aIn, 0);
+ return verifyCleartextSignature(aIn, outputStream, 0);
} else {
// else: ascii armored encryption! go on...
- return decryptVerify(cryptoInput, in, 0);
+ return decryptVerify(input, cryptoInput, in, outputStream, 0);
}
} else {
- return decryptVerify(cryptoInput, in, 0);
+ return decryptVerify(input, cryptoInput, in, outputStream, 0);
}
}
} catch (PGPException e) {
@@ -220,6 +171,13 @@ public class PgpDecryptVerify extends BaseOperation {
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();
@@ -228,10 +186,10 @@ public class PgpDecryptVerify extends BaseOperation {
}
}
- /**
- * Verify Keybase.io style signed literal data
- */
- private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent)
+ /**Verify signed plaintext data (PGP/INLINE). */
+ @NonNull
+ private DecryptVerifyResult verifySignedLiteralData(
+ PgpDecryptVerifyInputParcel input, InputStream in, OutputStream out, int indent)
throws IOException, PGPException {
OperationLog log = new OperationLog();
log.add(LogType.MSG_VL, indent);
@@ -282,9 +240,9 @@ public class PgpDecryptVerify extends BaseOperation {
}
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint());
- if (!(mRequiredSignerFingerprint.equals(fingerprint))) {
+ if (!(input.getRequiredSignerFingerprint().equals(fingerprint))) {
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
- Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + mRequiredSignerFingerprint +
+ Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + input.getRequiredSignerFingerprint() +
" got " + fingerprint + "!");
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
@@ -316,7 +274,7 @@ public class PgpDecryptVerify extends BaseOperation {
int length;
byte[] buffer = new byte[1 << 16];
while ((length = dataIn.read(buffer)) > 0) {
- mOutStream.write(buffer, 0, length);
+ out.write(buffer, 0, length);
signature.update(buffer, 0, length);
}
@@ -326,10 +284,6 @@ public class PgpDecryptVerify extends BaseOperation {
PGPSignatureList signatureList = (PGPSignatureList) pgpF.nextObject();
PGPSignature messageSignature = signatureList.get(signatureIndex);
- // these are not cleartext signatures!
- // TODO: what about binary signatures?
- signatureResultBuilder.setSignatureOnly(false);
-
// Verify signature and check binding signatures
boolean validSignature = signature.verify(messageSignature);
if (validSignature) {
@@ -341,8 +295,8 @@ public class PgpDecryptVerify extends BaseOperation {
OpenPgpSignatureResult signatureResult = signatureResultBuilder.build();
- if (signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED
- && signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED) {
+ if (signatureResult.getResult() != OpenPgpSignatureResult.RESULT_VALID_CONFIRMED
+ && signatureResult.getResult() != OpenPgpSignatureResult.RESULT_VALID_UNCONFIRMED) {
log.add(LogType.MSG_VL_ERROR_INTEGRITY_CHECK, indent);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
@@ -352,19 +306,22 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_VL_OK, indent);
// Return a positive result, with metadata and verification info
- DecryptVerifyResult result =
- new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
+ DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setSignatureResult(signatureResult);
+ result.setDecryptionResult(
+ new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
return result;
}
- /**
- * Decrypt and/or verifies binary or ascii armored pgp
- */
- private DecryptVerifyResult decryptVerify(CryptoInputParcel cryptoInput,
- InputStream in, int indent) throws IOException, PGPException {
+ /** 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 {
+ OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
+ OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder();
OperationLog log = new OperationLog();
log.add(LogType.MSG_DC, indent);
@@ -384,7 +341,7 @@ public class PgpDecryptVerify extends BaseOperation {
}
if (enc == null) {
- log.add(LogType.MSG_DC_ERROR_INVALID_SIGLIST, indent);
+ log.add(LogType.MSG_DC_ERROR_INVALID_DATA, indent);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
@@ -419,6 +376,7 @@ public class PgpDecryptVerify extends BaseOperation {
}
Passphrase passphrase = null;
+ boolean skippedDisallowedKey = false;
// go through all objects and find one we can decrypt
while (it.hasNext()) {
@@ -451,29 +409,31 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
continue;
}
- // get subkey which has been used for this encryption packet
- secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId);
- if (secretEncryptionKey == null) {
- // should actually never happen, so no need to be more specific.
- log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
- continue;
- }
// allow only specific keys for decryption?
- if (mAllowedKeyIds != null) {
+ if (input.getAllowedKeyIds() != null) {
long masterKeyId = secretKeyRing.getMasterKeyId();
Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
- Log.d(Constants.TAG, "mAllowedKeyIds: " + mAllowedKeyIds);
+ Log.d(Constants.TAG, "mAllowedKeyIds: " + input.getAllowedKeyIds());
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
- if (!mAllowedKeyIds.contains(masterKeyId)) {
+ if (!input.getAllowedKeyIds().contains(masterKeyId)) {
// this key is in our db, but NOT allowed!
// continue with the next packet in the while loop
+ skippedDisallowedKey = true;
log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1);
continue;
}
}
+ // get subkey which has been used for this encryption packet
+ secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId);
+ if (secretEncryptionKey == null) {
+ // should actually never happen, so no need to be more specific.
+ log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
+ continue;
+ }
+
/* secret key exists in database and is allowed! */
asymmetricPacketFound = true;
@@ -499,10 +459,17 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
return new DecryptVerifyResult(log,
RequiredInputParcel.createRequiredDecryptPassphrase(
- secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()));
+ secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()),
+ cryptoInput);
}
}
+ // check for insecure encryption key
+ if ( ! PgpSecurityConstants.isSecureKey(secretEncryptionKey)) {
+ log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
+ decryptionResultBuilder.setInsecure(true);
+ }
+
// break out of while, only decrypt the first packet where we have a key
break;
@@ -511,7 +478,7 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_SYM, indent);
- if (!mAllowSymmetricDecryption) {
+ if (!input.isAllowSymmetricDecryption()) {
log.add(LogType.MSG_DC_SYM_SKIP, indent + 1);
continue;
}
@@ -527,12 +494,24 @@ public class PgpDecryptVerify extends BaseOperation {
// 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;
@@ -568,7 +547,14 @@ public class PgpDecryptVerify extends BaseOperation {
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);
@@ -590,35 +576,60 @@ public class PgpDecryptVerify extends BaseOperation {
currentProgress += 2;
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
- try {
- PublicKeyDataDecryptorFactory decryptorFactory
- = secretEncryptionKey.getDecryptorFactory(cryptoInput);
- clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
+ CachingDataDecryptorFactory decryptorFactory
+ = secretEncryptionKey.getCachingDecryptorFactory(cryptoInput);
+
+ // special case: if the decryptor does not have a session key cached for this encrypted
+ // data, and can't actually decrypt on its own, return a pending intent
+ if (!decryptorFactory.canDecrypt()
+ && !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) {
- symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
- } catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) {
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
- e.encryptedSessionKey, secretEncryptionKey.getKeyId()
- ));
+ secretEncryptionKey.getRing().getMasterKeyId(),
+ secretEncryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
+ ),
+ cryptoInput);
+
+ }
+
+ try {
+ clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
+ } catch (PGPKeyValidationException | ArrayIndexOutOfBoundsException e) {
+ log.add(LogType.MSG_DC_ERROR_CORRUPT_DATA, indent + 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
+
+ symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
+
+ cryptoInput.addCryptoData(decryptorFactory.getCachedSessionKeys());
+
encryptedData = encryptedDataAsymmetric;
} else {
- // If we didn't find any useful data, error out
+ // there wasn't even any useful data
+ if (!anyPacketFound) {
+ log.add(LogType.MSG_DC_ERROR_NO_DATA, indent + 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_NO_DATA, log);
+ }
+ // there was data but key wasn't allowed
+ if (skippedDisallowedKey) {
+ log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_KEY_DISALLOWED, log);
+ }
// no packet has been found where we have the corresponding secret key in our db
- log.add(
- anyPacketFound ? LogType.MSG_DC_ERROR_NO_KEY : LogType.MSG_DC_ERROR_NO_DATA, indent + 1);
+ log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
+ decryptionResultBuilder.setEncrypted(true);
- // Warn about old encryption algorithms!
- if (!PgpConstants.sPreferredSymmetricAlgorithms.contains(symmetricEncryptionAlgo)) {
- log.add(LogType.MSG_DC_OLD_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
+ // Check for insecure encryption algorithms!
+ if (!PgpSecurityConstants.isSecureSymmetricAlgorithm(symmetricEncryptionAlgo)) {
+ log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
+ decryptionResultBuilder.setInsecure(true);
}
JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
Object dataChunk = plainFact.nextObject();
- OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
int signatureIndex = -1;
CanonicalizedPublicKeyRing signingRing = null;
CanonicalizedPublicKey signingKey = null;
@@ -682,6 +693,13 @@ public class PgpDecryptVerify extends BaseOperation {
}
}
+ // check for insecure signing key
+ // TODO: checks on signingRing ?
+ if (signingKey != null && ! PgpSecurityConstants.isSecureKey(signingKey)) {
+ log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
+ signatureResultBuilder.setInsecure(true);
+ }
+
dataChunk = plainFact.nextObject();
}
@@ -700,17 +718,12 @@ public class PgpDecryptVerify extends BaseOperation {
PGPLiteralData literalData = (PGPLiteralData) dataChunk;
- // reported size may be null if partial packets are involved (highly unlikely though)
- Long originalSize = literalData.getDataLengthIfAvailable();
-
String originalFilename = literalData.getFileName();
String mimeType = null;
if (literalData.getFormat() == PGPLiteralData.TEXT
|| literalData.getFormat() == PGPLiteralData.UTF8) {
mimeType = "text/plain";
} else {
- // TODO: better would be: https://github.com/open-keychain/open-keychain/issues/753
-
// try to guess from file ending
String extension = MimeTypeMap.getFileExtensionFromUrl(originalFilename);
if (extension != null) {
@@ -718,19 +731,10 @@ public class PgpDecryptVerify extends BaseOperation {
mimeType = mime.getMimeTypeFromExtension(extension);
}
if (mimeType == null) {
- mimeType = URLConnection.guessContentTypeFromName(originalFilename);
- }
- if (mimeType == null) {
- mimeType = "*/*";
+ mimeType = "application/octet-stream";
}
}
- metadata = new OpenPgpMetadata(
- originalFilename,
- mimeType,
- literalData.getModificationTime().getTime(),
- originalSize == null ? 0 : originalSize);
-
if (!"".equals(originalFilename)) {
log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename);
}
@@ -738,20 +742,31 @@ public class PgpDecryptVerify extends BaseOperation {
mimeType);
log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1,
new Date(literalData.getModificationTime().getTime()).toString());
- if (originalSize != null) {
- log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1,
- Long.toString(originalSize));
- } else {
- log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1);
- }
// return here if we want to decrypt the metadata only
- if (mDecryptMetadataOnly) {
+ if (input.isDecryptMetadataOnly()) {
+
+ // this operation skips the entire stream to find the data length!
+ Long originalSize = literalData.findDataLength();
+
+ if (originalSize != null) {
+ log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1,
+ Long.toString(originalSize));
+ } else {
+ log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1);
+ }
+
+ metadata = new OpenPgpMetadata(
+ originalFilename,
+ mimeType,
+ literalData.getModificationTime().getTime(),
+ originalSize == null ? 0 : originalSize);
+
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
DecryptVerifyResult result =
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setCharset(charset);
- result.setDecryptMetadata(metadata);
+ result.setDecryptionMetadata(metadata);
return result;
}
@@ -769,13 +784,13 @@ public class PgpDecryptVerify extends BaseOperation {
InputStream dataIn = literalData.getInputStream();
long alreadyWritten = 0;
- long wholeSize = mData.getSize() - mData.getStreamPosition();
+ long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition();
int length;
byte[] buffer = new byte[1 << 16];
while ((length = dataIn.read(buffer)) > 0) {
- Log.d(Constants.TAG, "read bytes: " + length);
- if (mOutStream != null) {
- mOutStream.write(buffer, 0, length);
+ // Log.d(Constants.TAG, "read bytes: " + length);
+ if (out != null) {
+ out.write(buffer, 0, length);
}
// update signature buffer if signature is also present
@@ -795,6 +810,12 @@ public class PgpDecryptVerify extends BaseOperation {
// TODO: slow annealing to fake a progress?
}
+ metadata = new OpenPgpMetadata(
+ originalFilename,
+ mimeType,
+ literalData.getModificationTime().getTime(),
+ alreadyWritten);
+
if (signature != null) {
updateProgress(R.string.progress_verifying_signature, 90, 100);
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
@@ -802,11 +823,9 @@ public class PgpDecryptVerify extends BaseOperation {
PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
PGPSignature messageSignature = signatureList.get(signatureIndex);
- // these are not cleartext signatures!
// TODO: what about binary signatures?
- signatureResultBuilder.setSignatureOnly(false);
- // Verify signature and check binding signatures
+ // Verify signature
boolean validSignature = signature.verify(messageSignature);
if (validSignature) {
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
@@ -814,10 +833,10 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
}
- // Don't allow verification of old hash algorithms!
- if (!PgpConstants.sPreferredHashAlgorithms.contains(signature.getHashAlgorithm())) {
- validSignature = false;
- log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1);
+ // check for insecure hash algorithms
+ if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
+ log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
+ signatureResultBuilder.setInsecure(true);
}
signatureResultBuilder.setValidSignature(validSignature);
@@ -844,8 +863,8 @@ public class PgpDecryptVerify extends BaseOperation {
// The MDC packet can be stripped by an attacker!
Log.d(Constants.TAG, "MDC fail");
if (!signatureResultBuilder.isValidSignature()) {
- log.add(LogType.MSG_DC_ERROR_INTEGRITY_MISSING, indent);
- return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
+ log.add(LogType.MSG_DC_INSECURE_MDC_MISSING, indent);
+ decryptionResultBuilder.setInsecure(true);
}
}
@@ -854,11 +873,12 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_OK, indent);
// Return a positive result, with metadata and verification info
- DecryptVerifyResult result =
- new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
- result.setDecryptMetadata(metadata);
+ DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
+ result.setCachedCryptoInputParcel(cryptoInput);
result.setSignatureResult(signatureResultBuilder.build());
result.setCharset(charset);
+ result.setDecryptionResult(decryptionResultBuilder.build());
+ result.setDecryptionMetadata(metadata);
return result;
}
@@ -870,14 +890,13 @@ public class PgpDecryptVerify extends BaseOperation {
* The method is heavily based on
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
*/
+ @NonNull
private DecryptVerifyResult verifyCleartextSignature(
- ArmoredInputStream aIn, int indent) throws IOException, PGPException {
+ ArmoredInputStream aIn, OutputStream outputStream, int indent) throws IOException, PGPException {
OperationLog log = new OperationLog();
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
- // cleartext signatures are never encrypted ;)
- signatureResultBuilder.setSignatureOnly(true);
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -901,8 +920,9 @@ public class PgpDecryptVerify extends BaseOperation {
out.close();
byte[] clearText = out.toByteArray();
- if (mOutStream != null) {
- mOutStream.write(clearText);
+ if (outputStream != null) {
+ outputStream.write(clearText);
+ outputStream.close();
}
updateProgress(R.string.progress_processing_signature, 60, 100);
@@ -910,11 +930,11 @@ public class PgpDecryptVerify extends BaseOperation {
PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
if (sigList == null) {
- log.add(LogType.MSG_DC_ERROR_INVALID_SIGLIST, 0);
+ log.add(LogType.MSG_DC_ERROR_INVALID_DATA, 0);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
- PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder);
+ PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder, log, indent);
if (signature != null) {
try {
@@ -946,10 +966,10 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
}
- // Don't allow verification of old hash algorithms!
- if (!PgpConstants.sPreferredHashAlgorithms.contains(signature.getHashAlgorithm())) {
- validSignature = false;
- log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1);
+ // check for insecure hash algorithms
+ if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
+ log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
+ signatureResultBuilder.setInsecure(true);
}
signatureResultBuilder.setValidSignature(validSignature);
@@ -964,22 +984,31 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_OK, indent);
+ OpenPgpMetadata metadata = new OpenPgpMetadata(
+ "",
+ "text/plain",
+ -1,
+ clearText.length);
+
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setSignatureResult(signatureResultBuilder.build());
+ result.setDecryptionResult(
+ new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
+ result.setDecryptionMetadata(metadata);
return result;
}
- private DecryptVerifyResult verifyDetachedSignature(InputStream in, int indent)
+ @NonNull
+ private DecryptVerifyResult verifyDetachedSignature(
+ PgpDecryptVerifyInputParcel input, InputData inputData, OutputStream out, int indent)
throws IOException, PGPException {
OperationLog log = new OperationLog();
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
- // detached signatures are never encrypted
- signatureResultBuilder.setSignatureOnly(true);
updateProgress(R.string.progress_processing_signature, 0, 100);
- InputStream detachedSigIn = new ByteArrayInputStream(mDetachedSignature);
+ InputStream detachedSigIn = new ByteArrayInputStream(input.getDetachedSignature());
detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn);
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(detachedSigIn);
@@ -993,23 +1022,24 @@ public class PgpDecryptVerify extends BaseOperation {
} else if (o instanceof PGPSignatureList) {
sigList = (PGPSignatureList) o;
} else {
- log.add(LogType.MSG_DC_ERROR_INVALID_SIGLIST, 0);
+ log.add(LogType.MSG_DC_ERROR_INVALID_DATA, 0);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
- PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder);
+ PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder, log, indent);
if (signature != null) {
updateProgress(R.string.progress_reading_data, 60, 100);
ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100);
long alreadyWritten = 0;
- long wholeSize = mData.getSize() - mData.getStreamPosition();
+ long wholeSize = inputData.getSize() - inputData.getStreamPosition();
int length;
byte[] buffer = new byte[1 << 16];
+ InputStream in = inputData.getInputStream();
while ((length = in.read(buffer)) > 0) {
- if (mOutStream != null) {
- mOutStream.write(buffer, 0, length);
+ if (out != null) {
+ out.write(buffer, 0, length);
}
// update signature buffer if signature is also present
@@ -1030,9 +1060,6 @@ public class PgpDecryptVerify extends BaseOperation {
updateProgress(R.string.progress_verifying_signature, 90, 100);
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
- // these are not cleartext signatures!
- signatureResultBuilder.setSignatureOnly(false);
-
// Verify signature and check binding signatures
boolean validSignature = signature.verify();
if (validSignature) {
@@ -1041,10 +1068,10 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
}
- // Don't allow verification of old hash algorithms!
- if (!PgpConstants.sPreferredHashAlgorithms.contains(signature.getHashAlgorithm())) {
- validSignature = false;
- log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1);
+ // check for insecure hash algorithms
+ if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
+ log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
+ signatureResultBuilder.setInsecure(true);
}
signatureResultBuilder.setValidSignature(validSignature);
@@ -1056,10 +1083,15 @@ public class PgpDecryptVerify extends BaseOperation {
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setSignatureResult(signatureResultBuilder.build());
+ result.setDecryptionResult(
+ new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
return result;
}
- private PGPSignature processPGPSignatureList(PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder) throws PGPException {
+ private PGPSignature processPGPSignatureList(
+ PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder,
+ OperationLog log, int indent)
+ throws PGPException {
CanonicalizedPublicKeyRing signingRing = null;
CanonicalizedPublicKey signingKey = null;
int signatureIndex = -1;
@@ -1100,6 +1132,13 @@ public class PgpDecryptVerify extends BaseOperation {
}
}
+ // check for insecure signing key
+ // TODO: checks on signingRing ?
+ if (signingKey != null && ! PgpSecurityConstants.isSecureKey(signingKey)) {
+ log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
+ signatureResultBuilder.setInsecure(true);
+ }
+
return signature;
}
@@ -1195,12 +1234,6 @@ public class PgpDecryptVerify extends BaseOperation {
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/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
index d8b86a18c..e8d1d3111 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
@@ -21,6 +21,8 @@ package org.sufficientlysecure.keychain.pgp;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -31,6 +33,7 @@ import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.SecureRandom;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PgpHelper {
@@ -52,9 +55,6 @@ public class PgpHelper {
* <p/>
* TODO: Does this really help on flash storage?
*
- * @param context
- * @param progressable
- * @param file
* @throws IOException
*/
public static void deleteFileSecurely(Context context, Progressable progressable, File file)
@@ -78,4 +78,67 @@ public class PgpHelper {
raf.close();
file.delete();
}
+
+ /**
+ * Fixing broken PGP MESSAGE Strings coming from GMail/AOSP Mail
+ */
+ public static String fixPgpMessage(String message) {
+ // windows newline -> unix newline
+ message = message.replaceAll("\r\n", "\n");
+ // Mac OS before X newline -> unix newline
+ message = message.replaceAll("\r", "\n");
+
+ // remove whitespaces before newline
+ message = message.replaceAll(" +\n", "\n");
+ // only two consecutive newlines are allowed
+ message = message.replaceAll("\n\n+", "\n\n");
+
+ // replace non breakable spaces
+ message = message.replaceAll("\\xa0", " ");
+
+ return message;
+ }
+
+ /**
+ * Fixing broken PGP SIGNED MESSAGE Strings coming from GMail/AOSP Mail
+ */
+ public static String fixPgpCleartextSignature(CharSequence input) {
+ if (!TextUtils.isEmpty(input)) {
+ String text = input.toString();
+
+ // windows newline -> unix newline
+ text = text.replaceAll("\r\n", "\n");
+ // Mac OS before X newline -> unix newline
+ text = text.replaceAll("\r", "\n");
+
+ return text;
+ } else {
+ return null;
+ }
+ }
+
+ public static String getPgpContent(@NonNull CharSequence input) {
+ Log.dEscaped(Constants.TAG, "input: " + input);
+
+ Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input);
+ if (matcher.matches()) {
+ String text = matcher.group(1);
+ text = fixPgpMessage(text);
+
+ Log.dEscaped(Constants.TAG, "input fixed: " + text);
+ return text;
+ } else {
+ matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input);
+ if (matcher.matches()) {
+ String text = matcher.group(1);
+ text = fixPgpCleartextSignature(text);
+
+ Log.dEscaped(Constants.TAG, "input fixed: " + text);
+ return text;
+ } else {
+ return null;
+ }
+ }
+ }
+
}
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 89db378a9..6f156c201 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
@@ -18,9 +18,11 @@
package org.sufficientlysecure.keychain.pgp;
+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;
@@ -45,8 +47,10 @@ import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
+import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
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.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
@@ -59,6 +63,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
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.service.input.RequiredInputParcel.NfcKeyToCardOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@@ -68,6 +73,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.IOException;
import java.math.BigInteger;
+import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
@@ -151,7 +157,7 @@ public class PgpKeyOperation {
}
/** Creates new secret key. */
- private PGPKeyPair createKey(SubkeyAdd add, OperationLog log, int indent) {
+ private PGPKeyPair createKey(SubkeyAdd add, Date creationTime, OperationLog log, int indent) {
try {
// Some safety checks
@@ -249,7 +255,7 @@ public class PgpKeyOperation {
}
// build new key pair
- return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
+ return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), creationTime);
} catch(NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
@@ -295,8 +301,10 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
+ Date creationTime = new Date();
+
subProgressPush(10, 30);
- PGPKeyPair keyPair = createKey(add, log, indent);
+ PGPKeyPair keyPair = createKey(add, creationTime, log, indent);
subProgressPop();
// return null if this failed (an error will already have been logged by createKey)
@@ -308,14 +316,14 @@ public class PgpKeyOperation {
// Build key encrypter and decrypter based on passphrase
PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder()
- .build().get(PgpConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
+ .build().get(PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
- PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO,
- encryptorHashCalc, PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
+ PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO,
+ encryptorHashCalc, PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
- .build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
+ .build().get(PgpSecurityConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
PGPSecretKey masterSecretKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, true, keyEncryptor);
@@ -323,8 +331,8 @@ public class PgpKeyOperation {
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
subProgressPush(50, 100);
- CryptoInputParcel cryptoInput = new CryptoInputParcel(new Date(), new Passphrase(""));
- return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, cryptoInput, saveParcel, log);
+ CryptoInputParcel cryptoInput = new CryptoInputParcel(creationTime, new Passphrase(""));
+ return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, cryptoInput, saveParcel, log, indent);
} catch (PGPException e) {
log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
@@ -356,8 +364,8 @@ public class PgpKeyOperation {
*
*/
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
- CryptoInputParcel cryptoInput,
- SaveKeyringParcel saveParcel) {
+ CryptoInputParcel cryptoInput,
+ SaveKeyringParcel saveParcel) {
OperationLog log = new OperationLog();
int indent = 0;
@@ -400,9 +408,61 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
+ // Ensure we don't have multiple keys for the same slot.
+ boolean hasSign = false;
+ boolean hasEncrypt = false;
+ boolean hasAuth = false;
+ for(SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) {
+ if (change.mMoveKeyToCard) {
+ // If this is a keytocard operation, see if it was completed: look for a hash
+ // matching the given subkey ID in cryptoData.
+ byte[] subKeyId = new byte[8];
+ ByteBuffer buf = ByteBuffer.wrap(subKeyId);
+ buf.putLong(change.mKeyId).rewind();
+
+ byte[] serialNumber = cryptoInput.getCryptoData().get(buf);
+ if (serialNumber != null) {
+ change.mMoveKeyToCard = false;
+ change.mDummyDivert = serialNumber;
+ }
+ }
+
+ if (change.mMoveKeyToCard) {
+ // Pending keytocard operation. Need to make sure that we don't have multiple
+ // subkeys pending for the same slot.
+ CanonicalizedSecretKey wsK = wsKR.getSecretKey(change.mKeyId);
+
+ if ((wsK.canSign() || wsK.canCertify())) {
+ if (hasSign) {
+ log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ } else {
+ hasSign = true;
+ }
+ } else if ((wsK.canEncrypt())) {
+ if (hasEncrypt) {
+ log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ } else {
+ hasEncrypt = true;
+ }
+ } else if ((wsK.canAuthenticate())) {
+ if (hasAuth) {
+ log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ } else {
+ hasAuth = true;
+ }
+ } else {
+ log.add(LogType.MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD, indent + 1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+ }
+ }
+
if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) {
log.add(LogType.MSG_MF_RESTRICTED_MODE, indent);
- return internalRestricted(sKR, saveParcel, log);
+ return internalRestricted(sKR, saveParcel, log, indent + 1);
}
// Do we require a passphrase? If so, pass it along
@@ -410,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.
@@ -420,7 +480,7 @@ public class PgpKeyOperation {
Date expiryTime = wsKR.getPublicKey().getExpiryTime();
long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L;
- return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, cryptoInput, saveParcel, log);
+ return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, cryptoInput, saveParcel, log, indent);
}
@@ -428,13 +488,14 @@ public class PgpKeyOperation {
int masterKeyFlags, long masterKeyExpiry,
CryptoInputParcel cryptoInput,
SaveKeyringParcel saveParcel,
- OperationLog log) {
-
- int indent = 1;
+ OperationLog log,
+ int indent) {
NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(
cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
masterSecretKey.getKeyID());
+ NfcKeyToCardOperationsBuilder nfcKeyToCardOps = new NfcKeyToCardOperationsBuilder(
+ masterSecretKey.getKeyID());
progress(R.string.progress_modify, 0);
@@ -553,7 +614,8 @@ public class PgpKeyOperation {
PGPSignature cert = generateUserAttributeSignature(
getSignatureGenerator(masterSecretKey, cryptoInput),
cryptoInput.getSignatureTime(),
- masterPrivateKey, masterPublicKey, vector);
+ masterPrivateKey, masterPublicKey, vector,
+ masterKeyFlags, masterKeyExpiry);
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
} catch (NfcInteractionNeeded e) {
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
@@ -743,22 +805,36 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
- if (change.mDummyStrip || change.mDummyDivert != null) {
+ if (change.mDummyStrip) {
// IT'S DANGEROUS~
// no really, it is. this operation irrevocably removes the private key data from the key
- if (change.mDummyStrip) {
- sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
+ sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
+ } else if (change.mMoveKeyToCard) {
+ if (checkSmartCardCompatibility(sKey, log, indent + 1)) {
+ log.add(LogType.MSG_MF_KEYTOCARD_START, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ nfcKeyToCardOps.addSubkey(change.mKeyId);
} else {
- // the serial number must be 16 bytes in length
- if (change.mDummyDivert.length != 16) {
- log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
- indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
- return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
- }
+ // Appropriate log message already set by checkSmartCardCompatibility
+ return new PgpEditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
+ } else if (change.mDummyDivert != null) {
+ // NOTE: Does this code get executed? Or always handled in internalRestricted?
+ if (change.mDummyDivert.length != 16) {
+ log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
+ indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+ log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(change.mKeyId),
+ Hex.toHexString(change.mDummyDivert, 8, 6));
+ sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
}
+
+
// This doesn't concern us any further
if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) {
continue;
@@ -822,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();
@@ -884,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);
@@ -899,7 +997,7 @@ public class PgpKeyOperation {
(i-1) * (100 / saveParcel.mAddSubKeys.size()),
i * (100 / saveParcel.mAddSubKeys.size())
);
- PGPKeyPair keyPair = createKey(add, log, indent);
+ PGPKeyPair keyPair = createKey(add, cryptoInput.getSignatureTime(), log, indent);
subProgressPop();
if (keyPair == null) {
log.add(LogType.MSG_MF_ERROR_PGP, indent +1);
@@ -912,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) {
@@ -922,15 +1021,15 @@ public class PgpKeyOperation {
PGPSecretKey sKey; {
// Build key encrypter and decrypter based on passphrase
PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder()
- .build().get(PgpConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
+ .build().get(PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
- PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
- PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
+ PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
+ PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
cryptoInput.getPassphrase().getCharArray());
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
- .build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
+ .build().get(PgpSecurityConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey, sha1Calc, false, keyEncryptor);
}
@@ -964,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);
@@ -980,9 +1099,19 @@ public class PgpKeyOperation {
progress(R.string.progress_done, 100);
+ if (!nfcSignOps.isEmpty() && !nfcKeyToCardOps.isEmpty()) {
+ log.add(LogType.MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS, indent+1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+
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(), cryptoInput);
}
log.add(LogType.MSG_MF_SUCCESS, indent);
@@ -995,9 +1124,7 @@ public class PgpKeyOperation {
* otherwise.
*/
private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel,
- OperationLog log) {
-
- int indent = 1;
+ OperationLog log, int indent) {
progress(R.string.progress_modify, 0);
@@ -1042,6 +1169,9 @@ public class PgpKeyOperation {
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
+ log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(change.mKeyId),
+ Hex.toHexString(change.mDummyDivert, 8, 6));
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
}
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
@@ -1076,7 +1206,7 @@ public class PgpKeyOperation {
// add packet with EMPTY notation data (updates old one, but will be stripped later)
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
- PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
+ PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
{ // set subpackets
@@ -1103,7 +1233,7 @@ public class PgpKeyOperation {
// add packet with "pin" notation data
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
- PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
+ PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
{ // set subpackets
@@ -1150,13 +1280,13 @@ public class PgpKeyOperation {
OperationLog log, int indent) throws PGPException {
PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder().build()
- .get(PgpConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
+ .get(PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
// Build key encryptor based on new passphrase
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
- PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
- PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
+ PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
+ PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray());
// noinspection unchecked
@@ -1296,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(), PgpSecurityConstants.SECRET_KEY_BINDING_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(), PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
}
@@ -1319,11 +1454,9 @@ public class PgpKeyOperation {
}
- private PGPSignature generateUserIdSignature(
- PGPSignatureGenerator sGen, Date creationTime,
- PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary,
- int flags, long expiry)
- throws IOException, PGPException, SignatureException {
+ private static PGPSignatureSubpacketGenerator generateHashedSelfSigSubpackets(
+ Date creationTime, PGPPublicKey pKey, boolean primary, int flags, long expiry
+ ) {
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
{
@@ -1339,11 +1472,11 @@ public class PgpKeyOperation {
*/
/* non-critical subpackets: */
hashedPacketsGen.setPreferredSymmetricAlgorithms(false,
- PgpConstants.getAsArray(PgpConstants.sPreferredSymmetricAlgorithms));
+ PgpSecurityConstants.PREFERRED_SYMMETRIC_ALGORITHMS);
hashedPacketsGen.setPreferredHashAlgorithms(false,
- PgpConstants.getAsArray(PgpConstants.sPreferredHashAlgorithms));
+ PgpSecurityConstants.PREFERRED_HASH_ALGORITHMS);
hashedPacketsGen.setPreferredCompressionAlgorithms(false,
- PgpConstants.getAsArray(PgpConstants.sPreferredCompressionAlgorithms));
+ PgpSecurityConstants.PREFERRED_COMPRESSION_ALGORITHMS);
hashedPacketsGen.setPrimaryUserID(false, primary);
/* critical subpackets: we consider those important for a modern pgp implementation */
@@ -1357,6 +1490,17 @@ public class PgpKeyOperation {
}
}
+ return hashedPacketsGen;
+ }
+
+ private static PGPSignature generateUserIdSignature(
+ PGPSignatureGenerator sGen, Date creationTime,
+ PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary,
+ int flags, long expiry)
+ throws IOException, PGPException, SignatureException {
+
+ PGPSignatureSubpacketGenerator hashedPacketsGen =
+ generateHashedSelfSigSubpackets(creationTime, pKey, primary, flags, expiry);
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
return sGen.generateCertification(userId, pKey);
@@ -1365,15 +1509,12 @@ public class PgpKeyOperation {
private static PGPSignature generateUserAttributeSignature(
PGPSignatureGenerator sGen, Date creationTime,
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey,
- PGPUserAttributeSubpacketVector vector)
+ PGPUserAttributeSubpacketVector vector,
+ int flags, long expiry)
throws IOException, PGPException, SignatureException {
- PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
- {
- /* critical subpackets: we consider those important for a modern pgp implementation */
- hashedPacketsGen.setSignatureCreationTime(true, creationTime);
- }
-
+ PGPSignatureSubpacketGenerator hashedPacketsGen =
+ generateHashedSelfSigSubpackets(creationTime, pKey, false, flags, expiry);
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
return sGen.generateCertification(vector, pKey);
@@ -1385,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);
@@ -1397,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
@@ -1412,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();
@@ -1422,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);
@@ -1471,14 +1615,39 @@ public class PgpKeyOperation {
private static boolean isDummy(PGPSecretKey secretKey) {
S2K s2k = secretKey.getS2K();
- return s2k.getType() == S2K.GNU_DUMMY_S2K
- && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY;
+ return s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K
+ && s2k.getProtectionMode() != S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
}
private static boolean isDivertToCard(PGPSecretKey secretKey) {
S2K s2k = secretKey.getS2K();
- return s2k.getType() == S2K.GNU_DUMMY_S2K
+ return s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
}
+ private static boolean checkSmartCardCompatibility(PGPSecretKey key, OperationLog log, int indent) {
+ PGPPublicKey publicKey = key.getPublicKey();
+ int algorithm = publicKey.getAlgorithm();
+ if (algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT &&
+ algorithm != PublicKeyAlgorithmTags.RSA_SIGN &&
+ algorithm != PublicKeyAlgorithmTags.RSA_GENERAL) {
+ log.add(LogType.MSG_MF_ERROR_BAD_NFC_ALGO, indent + 1);
+ return false;
+ }
+
+ // Key size must be 2048
+ int keySize = publicKey.getBitStrength();
+ if (keySize != 2048) {
+ log.add(LogType.MSG_MF_ERROR_BAD_NFC_SIZE, indent + 1);
+ return false;
+ }
+
+ // Secret key parts must be available
+ if (isDivertToCard(key) || isDummy(key)) {
+ log.add(LogType.MSG_MF_ERROR_BAD_NFC_STRIPPED, indent + 1);
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java
new file mode 100644
index 000000000..3fa549946
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java
@@ -0,0 +1,262 @@
+/*
+ * 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.pgp;
+
+import org.spongycastle.asn1.nist.NISTNamedCurves;
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+
+import java.util.HashSet;
+
+/**
+ * NIST requirements for 2011-2030 (http://www.keylength.com/en/4/):
+ * - RSA: 2048 bit
+ * - ECC: 224 bit
+ * - Symmetric: 3TDEA
+ * - Digital Signature (hash A): SHA-224 - SHA-512
+ *
+ * Extreme Decisions for Yahoo's End-to-End:
+ * https://github.com/yahoo/end-to-end/issues/31
+ * https://gist.github.com/coruus/68a8c65571e2b4225a69
+ */
+public class PgpSecurityConstants {
+
+ /**
+ * Whitelist of accepted symmetric encryption algorithms
+ * all other algorithms are rejected with OpenPgpDecryptionResult.RESULT_INSECURE
+ */
+ private static HashSet<Integer> sSymmetricAlgorithmsWhitelist = new HashSet<>();
+ static {
+ // General remarks: We try to keep the whitelist short to reduce attack surface
+ // TODO: block IDEA?: Bad key schedule (weak keys), implementation difficulties (easy to make errors)
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.IDEA);
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.TRIPLE_DES); // a MUST in RFC
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.CAST5); // default in many gpg, pgp versions, 128 bit key
+ // BLOWFISH: Twofish is the successor
+ // SAFER: not used widely
+ // DES: < 128 bit security
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_128);
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_192);
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_256);
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.TWOFISH); // 128 bit
+ // CAMELLIA_128: not used widely
+ // CAMELLIA_192: not used widely
+ // CAMELLIA_256: not used widely
+ }
+
+ public static boolean isSecureSymmetricAlgorithm(int id) {
+ return sSymmetricAlgorithmsWhitelist.contains(id);
+ }
+
+ /**
+ * Whitelist of accepted hash algorithms
+ * all other algorithms are rejected with OpenPgpSignatureResult.RESULT_INSECURE
+ *
+ * coorus:
+ * Implementations SHOULD use SHA-512 for RSA or DSA signatures. They SHOULD NOT use SHA-384.
+ * ((cite to affine padding attacks; unproven status of RSA-PKCSv15))
+ *
+ * Implementations MUST NOT sign SHA-224 hashes. They SHOULD NOT accept signatures over SHA-224 hashes.
+ * ((collision resistance of 112-bits))
+ * Implementations SHOULD NOT sign SHA-256 hashes. They MUST NOT default to signing SHA-256 hashes.
+ */
+ private static HashSet<Integer> sHashAlgorithmsWhitelist = new HashSet<>();
+ static {
+ // MD5: broken
+ // SHA1: broken
+ // RIPEMD160: same security properties as SHA1
+ // DOUBLE_SHA: not used widely
+ // MD2: not used widely
+ // TIGER_192: not used widely
+ // HAVAL_5_160: not used widely
+ sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA256); // compatibility for old Mailvelope versions
+ sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA384);
+ sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA512);
+ // SHA224: Not used widely, Yahoo argues against it
+ }
+
+ public static boolean isSecureHashAlgorithm(int id) {
+ return sHashAlgorithmsWhitelist.contains(id);
+ }
+
+ /**
+ * Whitelist of accepted asymmetric algorithms in switch statement
+ * all other algorithms are rejected with OpenPgpSignatureResult.RESULT_INSECURE or
+ * OpenPgpDecryptionResult.RESULT_INSECURE
+ *
+ * coorus:
+ * Implementations MUST NOT accept, or treat any signature as valid, by an RSA key with
+ * bitlength less than 1023 bits.
+ * Implementations MUST NOT accept any RSA keys with bitlength less than 2047 bits after January 1, 2016.
+ */
+ private static HashSet<String> sCurveWhitelist = new HashSet<>();
+ static {
+ sCurveWhitelist.add(NISTNamedCurves.getOID("P-256").getId());
+ sCurveWhitelist.add(NISTNamedCurves.getOID("P-384").getId());
+ sCurveWhitelist.add(NISTNamedCurves.getOID("P-521").getId());
+ }
+
+ public static boolean isSecureKey(CanonicalizedPublicKey key) {
+ switch (key.getAlgorithm()) {
+ case PublicKeyAlgorithmTags.RSA_GENERAL: {
+ return (key.getBitStrength() >= 2048);
+ }
+ // RSA_ENCRYPT, RSA_SIGN: deprecated in RFC 4880, use RSA_GENERAL with key flags
+ case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: {
+ return (key.getBitStrength() >= 2048);
+ }
+ case PublicKeyAlgorithmTags.DSA: {
+ return (key.getBitStrength() >= 2048);
+ }
+ case PublicKeyAlgorithmTags.ECDH:
+ case PublicKeyAlgorithmTags.ECDSA: {
+ return PgpSecurityConstants.sCurveWhitelist.contains(key.getCurveOid());
+ }
+ // ELGAMAL_GENERAL: deprecated in RFC 4880, use ELGAMAL_ENCRYPT
+ // DIFFIE_HELLMAN: unsure
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * These array is written as a list of preferred encryption algorithms into keys created by us.
+ * Other implementations may choose to honor this selection.
+ * (Most preferred is first)
+ *
+ * REASON: See corresponding whitelist. AES received most cryptanalysis over the years
+ * and is still secure!
+ */
+ public static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
+ SymmetricKeyAlgorithmTags.AES_256,
+ SymmetricKeyAlgorithmTags.AES_192,
+ SymmetricKeyAlgorithmTags.AES_128,
+ };
+
+ /**
+ * These array is written as a list of preferred hash algorithms into keys created by us.
+ * Other implementations may choose to honor this selection.
+ * (Most preferred is first)
+ *
+ * REASON: See corresponding whitelist. If possible use SHA-512, this is state of the art!
+ */
+ public static final int[] PREFERRED_HASH_ALGORITHMS = new int[]{
+ HashAlgorithmTags.SHA512,
+ };
+
+ /**
+ * These array is written as a list of preferred compression algorithms into keys created by us.
+ * Other implementations may choose to honor this selection.
+ * (Most preferred is first)
+ *
+ * REASON: See DEFAULT_COMPRESSION_ALGORITHM
+ */
+ public static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[]{
+ CompressionAlgorithmTags.ZIP,
+ };
+
+ /**
+ * Hash algorithm used to certify public keys
+ */
+ public static final int CERTIFY_HASH_ALGO = HashAlgorithmTags.SHA512;
+
+
+ /**
+ * Always use AES-256! We always ignore the preferred encryption algos of the recipient!
+ *
+ * coorus:
+ * Implementations SHOULD ignore the symmetric algorithm preferences of a recipient's public key;
+ * in particular, implementations MUST NOT choose an algorithm forbidden by this
+ * document because a recipient prefers it.
+ *
+ * NEEDCITE downgrade attacks on TLS, other protocols
+ */
+ public static final int DEFAULT_SYMMETRIC_ALGORITHM = SymmetricKeyAlgorithmTags.AES_256;
+
+ public interface OpenKeychainSymmetricKeyAlgorithmTags extends SymmetricKeyAlgorithmTags {
+ int USE_DEFAULT = -1;
+ }
+
+ /**
+ * Always use SHA-512! We always ignore the preferred hash algos of the recipient!
+ *
+ * coorus:
+ * Implementations MUST ignore the hash algorithm preferences of a recipient when signing
+ * a message to a recipient. The difficulty of forging a signature under a given key,
+ * using generic attacks on hash functions, is the difficulty of the weakest hash signed by that key.
+ *
+ * Implementations MUST default to using SHA-512 for RSA signatures,
+ *
+ * and either SHA-512 or the matched instance of SHA-2 for ECDSA signatures.
+ * TODO: Ed25519
+ * CITE: zooko's hash function table CITE: distinguishers on SHA-256
+ */
+ public static final int DEFAULT_HASH_ALGORITHM = HashAlgorithmTags.SHA512;
+
+ public interface OpenKeychainHashAlgorithmTags extends HashAlgorithmTags {
+ int USE_DEFAULT = -1;
+ }
+
+ /**
+ * Compression is disabled by default.
+ *
+ * The default compression algorithm is only used if explicitly enabled in the activity's
+ * overflow menu or via the OpenPGP API's extra OpenPgpApi.EXTRA_ENABLE_COMPRESSION
+ *
+ * REASON: Enabling compression can lead to a sidechannel. Consider a voting that is done via
+ * OpenPGP. Compression can lead to different ciphertext lengths based on the user's voting.
+ * This has happened in a voting done by Wikipedia (Google it).
+ *
+ * ZLIB: the format provides no benefits over DEFLATE, and is more malleable
+ * BZIP2: very slow
+ */
+ public static final int DEFAULT_COMPRESSION_ALGORITHM = CompressionAlgorithmTags.ZIP;
+
+ public interface OpenKeychainCompressionAlgorithmTags extends CompressionAlgorithmTags {
+ int USE_DEFAULT = -1;
+ }
+
+ /**
+ * Note: s2kcount is a number between 0 and 0xff that controls the
+ * number of times to iterate the password hash before use. More
+ * iterations are useful against offline attacks, as it takes more
+ * time to check each password. The actual number of iterations is
+ * rather complex, and also depends on the hash function in use.
+ * Refer to Section 3.7.1.3 in rfc4880.txt. Bigger numbers give
+ * you more iterations. As a rough rule of thumb, when using
+ * SHA256 as the hashing function, 0x10 gives you about 64
+ * iterations, 0x20 about 128, 0x30 about 256 and so on till 0xf0,
+ * or about 1 million iterations. The maximum you can go to is
+ * 0xff, or about 2 million iterations.
+ * from http://kbsriram.com/2013/01/generating-rsa-keys-with-bouncycastle.html
+ *
+ * Bouncy Castle default: 0x60
+ * kbsriram proposes: 0xc0
+ * Yahoo's End-to-End: 96=0x60 (65536 iterations) (https://github.com/yahoo/end-to-end/blob/master/src/javascript/crypto/e2e/openpgp/keyring.js)
+ */
+ public static final int SECRET_KEY_ENCRYPTOR_S2K_COUNT = 0x90;
+ public static final int SECRET_KEY_ENCRYPTOR_HASH_ALGO = HashAlgorithmTags.SHA512;
+ public static final int SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO = SymmetricKeyAlgorithmTags.AES_256;
+ public static final int SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO = HashAlgorithmTags.SHA512;
+ // NOTE: only SHA1 is supported for key checksum calculations in OpenPGP,
+ // see http://tools.ietf.org/html/rfc488 0#section-5.5.3
+ public static final int SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO = HashAlgorithmTags.SHA1;
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java
index fd3c4910c..36d1a07cb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java
@@ -20,13 +20,8 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
-import java.nio.ByteBuffer;
-import java.util.Date;
-import java.util.Map;
-
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,19 +30,20 @@ public class PgpSignEncryptInputParcel implements Parcelable {
protected String mVersionHeader = null;
protected boolean mEnableAsciiArmorOutput = false;
- protected int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED;
+ protected int mCompressionAlgorithm = CompressionAlgorithmTags.UNCOMPRESSED;
protected long[] mEncryptionMasterKeyIds = null;
protected Passphrase mSymmetricPassphrase = null;
- protected int mSymmetricEncryptionAlgorithm = PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED;
+ protected int mSymmetricEncryptionAlgorithm = PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT;
protected long mSignatureMasterKeyId = Constants.key.none;
protected Long mSignatureSubKeyId = null;
- protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED;
+ protected int mSignatureHashAlgorithm = PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT;
protected long mAdditionalEncryptId = Constants.key.none;
protected boolean mFailOnMissingEncryptionKeyIds = false;
protected String mCharset;
protected boolean mCleartextSignature;
protected boolean mDetachedSignature = false;
protected boolean mHiddenRecipients = false;
+ protected boolean mIntegrityProtected = true;
public PgpSignEncryptInputParcel() {
@@ -60,7 +56,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
mVersionHeader = source.readString();
mEnableAsciiArmorOutput = source.readInt() == 1;
- mCompressionId = source.readInt();
+ mCompressionAlgorithm = source.readInt();
mEncryptionMasterKeyIds = source.createLongArray();
mSymmetricPassphrase = source.readParcelable(loader);
mSymmetricEncryptionAlgorithm = source.readInt();
@@ -73,6 +69,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
mCleartextSignature = source.readInt() == 1;
mDetachedSignature = source.readInt() == 1;
mHiddenRecipients = source.readInt() == 1;
+ mIntegrityProtected = source.readInt() == 1;
}
@Override
@@ -84,7 +81,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mVersionHeader);
dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0);
- dest.writeInt(mCompressionId);
+ dest.writeInt(mCompressionAlgorithm);
dest.writeLongArray(mEncryptionMasterKeyIds);
dest.writeParcelable(mSymmetricPassphrase, 0);
dest.writeInt(mSymmetricEncryptionAlgorithm);
@@ -102,6 +99,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
dest.writeInt(mCleartextSignature ? 1 : 0);
dest.writeInt(mDetachedSignature ? 1 : 0);
dest.writeInt(mHiddenRecipients ? 1 : 0);
+ dest.writeInt(mIntegrityProtected ? 1 : 0);
}
public String getCharset() {
@@ -179,12 +177,12 @@ public class PgpSignEncryptInputParcel implements Parcelable {
return this;
}
- public int getCompressionId() {
- return mCompressionId;
+ public int getCompressionAlgorithm() {
+ return mCompressionAlgorithm;
}
- public PgpSignEncryptInputParcel setCompressionId(int compressionId) {
- mCompressionId = compressionId;
+ public PgpSignEncryptInputParcel setCompressionAlgorithm(int compressionAlgorithm) {
+ mCompressionAlgorithm = compressionAlgorithm;
return this;
}
@@ -234,6 +232,18 @@ public class PgpSignEncryptInputParcel implements Parcelable {
return this;
}
+ public boolean isIntegrityProtected() {
+ return mIntegrityProtected;
+ }
+
+ /**
+ * Only use for testing! Never disable integrity protection!
+ */
+ public PgpSignEncryptInputParcel setIntegrityProtected(boolean integrityProtected) {
+ this.mIntegrityProtected = integrityProtected;
+ return this;
+ }
+
public boolean isHiddenRecipients() {
return mHiddenRecipients;
}
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 9073e81b9..29b2ef727 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;
@@ -60,9 +62,9 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
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 +101,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
*/
@@ -114,7 +123,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
boolean enableSignature = input.getSignatureMasterKeyId() != Constants.key.none;
boolean enableEncryption = ((input.getEncryptionMasterKeyIds() != null && input.getEncryptionMasterKeyIds().length > 0)
|| input.getSymmetricPassphrase() != null);
- boolean enableCompression = (input.getCompressionId() != CompressionAlgorithmTags.UNCOMPRESSED);
+ boolean enableCompression = (input.getCompressionAlgorithm() != CompressionAlgorithmTags.UNCOMPRESSED);
Log.d(Constants.TAG, "enableSignature:" + enableSignature
+ "\nenableEncryption:" + enableEncryption
@@ -189,7 +198,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);
@@ -216,15 +225,10 @@ public class PgpSignEncryptOperation extends BaseOperation {
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
}
- // Use preferred hash algo
+ // Use requested hash algo
int requestedAlgorithm = input.getSignatureHashAlgorithm();
- ArrayList<Integer> supported = signingKey.getSupportedHashAlgorithms();
- if (requestedAlgorithm == PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED) {
- // get most preferred
- input.setSignatureHashAlgorithm(supported.get(0));
- } else if (!supported.contains(requestedAlgorithm)) {
- log.add(LogType.MSG_PSE_ERROR_HASH_ALGO, indent);
- return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ if (requestedAlgorithm == PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT) {
+ input.setSignatureHashAlgorithm(PgpSecurityConstants.DEFAULT_HASH_ALGORITHM);
}
}
updateProgress(R.string.progress_preparing_streams, 2, 100);
@@ -233,18 +237,15 @@ public class PgpSignEncryptOperation extends BaseOperation {
PGPEncryptedDataGenerator cPk = null;
if (enableEncryption) {
- // Use preferred encryption algo
+ // Use requested encryption algo
int algo = input.getSymmetricEncryptionAlgorithm();
- if (algo == PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED) {
- // get most preferred
- // TODO: get from recipients
- algo = PgpConstants.sPreferredSymmetricAlgorithms.get(0);
+ if (algo == PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT) {
+ algo = PgpSecurityConstants.DEFAULT_SYMMETRIC_ALGORITHM;
}
- // has Integrity packet enabled!
JcePGPDataEncryptorBuilder encryptorBuilder =
new JcePGPDataEncryptorBuilder(algo)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
- .setWithIntegrityPacket(true);
+ .setWithIntegrityPacket(input.isIntegrityProtected());
cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
@@ -263,15 +264,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,
@@ -327,7 +332,13 @@ public class PgpSignEncryptOperation extends BaseOperation {
if (enableCompression) {
log.add(LogType.MSG_PSE_COMPRESSING, indent);
- compressGen = new PGPCompressedDataGenerator(input.getCompressionId());
+
+ // Use preferred compression algo
+ int algo = input.getCompressionAlgorithm();
+ if (algo == PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) {
+ algo = PgpSecurityConstants.DEFAULT_COMPRESSION_ALGORITHM;
+ }
+ compressGen = new PGPCompressedDataGenerator(algo);
bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));
} else {
bcpgOut = new BCPGOutputStream(encryptionOut);
@@ -450,7 +461,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
InputStream in = inputData.getInputStream();
if (enableCompression) {
- compressGen = new PGPCompressedDataGenerator(input.getCompressionId());
+ compressGen = new PGPCompressedDataGenerator(input.getCompressionAlgorithm());
bcpgOut = new BCPGOutputStream(compressGen.open(out));
} else {
bcpgOut = new BCPGOutputStream(out);
@@ -497,7 +508,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
// this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
log.add(LogType.MSG_PSE_PENDING_NFC, indent);
return new PgpSignEncryptResult(log, RequiredInputParcel.createNfcSignOperation(
- e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()));
+ signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(),
+ e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()), cryptoInput);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java
index 464de37f5..8f80a4802 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java
@@ -57,6 +57,10 @@ public class SignEncryptParcel extends PgpSignEncryptInputParcel {
}
+ public boolean isIncomplete() {
+ return mInputUris.size() > mOutputUris.size();
+ }
+
public byte[] getBytes() {
return mBytes;
}
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 2bb4f7dc4..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;
@@ -219,7 +220,7 @@ public class UncachedKeyRing {
Iterator<PGPPublicKey> it = mRing.getPublicKeys();
while (it.hasNext()) {
if (KeyFormattingUtils.convertFingerprintToHex(
- it.next().getFingerprint()).equals(expectedFingerprint)) {
+ it.next().getFingerprint()).equalsIgnoreCase(expectedFingerprint)) {
return true;
}
}
@@ -820,6 +821,15 @@ public class UncachedKeyRing {
continue;
}
+ Date keyCreationTime = key.getCreationTime(), keyCreationTimeLenient;
+ {
+ Calendar keyCreationCal = Calendar.getInstance();
+ keyCreationCal.setTime(keyCreationTime);
+ // allow for diverging clocks up to one day when checking creation time
+ keyCreationCal.add(Calendar.MINUTE, -5);
+ keyCreationTimeLenient = keyCreationCal.getTime();
+ }
+
// A subkey needs exactly one subkey binding certificate, and optionally one revocation
// certificate.
PGPPublicKey modified = key;
@@ -851,6 +861,18 @@ public class UncachedKeyRing {
continue;
}
+ if (cert.getCreationTime().before(keyCreationTime)) {
+ // Signature is earlier than key creation time
+ log.add(LogType.MSG_KC_SUB_BAD_TIME_EARLY, indent);
+ // due to an earlier accident, we generated keys which had creation timestamps
+ // a few seconds after their signature timestamp. for compatibility, we only
+ // error out with some margin of error
+ if (cert.getCreationTime().before(keyCreationTimeLenient)) {
+ badCerts += 1;
+ continue;
+ }
+ }
+
if (cert.isLocal()) {
// Creation date in the future? No way!
log.add(LogType.MSG_KC_SUB_BAD_LOCAL, indent);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
index 0173a1d83..013a6bf14 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
@@ -211,12 +211,19 @@ public class UncachedPublicKey {
return getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT;
}
+ public boolean isRSA() {
+ return getAlgorithm() == PGPPublicKey.RSA_GENERAL
+ || getAlgorithm() == PGPPublicKey.RSA_ENCRYPT
+ || getAlgorithm() == PGPPublicKey.RSA_SIGN;
+ }
+
public boolean isDSA() {
return getAlgorithm() == PGPPublicKey.DSA;
}
public boolean isEC() {
- return getAlgorithm() == PGPPublicKey.ECDH || getAlgorithm() == PGPPublicKey.ECDSA;
+ return getAlgorithm() == PGPPublicKey.ECDH
+ || getAlgorithm() == PGPPublicKey.ECDSA;
}
public byte[] getFingerprint() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
index 11d6728e2..ee28b5f36 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
@@ -51,6 +51,11 @@ public class KeychainContract {
String EXPIRY = "expiry";
}
+ interface UpdatedKeysColumns {
+ String MASTER_KEY_ID = "master_key_id"; // not a database id
+ String LAST_UPDATED = "last_updated"; // time since epoch in seconds
+ }
+
interface UserPacketsColumns {
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
String TYPE = "type"; // not a database id
@@ -90,13 +95,15 @@ public class KeychainContract {
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
}
- public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".provider";
+ public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY;
private static final Uri BASE_CONTENT_URI_INTERNAL = Uri
.parse("content://" + CONTENT_AUTHORITY);
public static final String BASE_KEY_RINGS = "key_rings";
+ public static final String BASE_UPDATED_KEYS = "updated_keys";
+
public static final String PATH_UNIFIED = "unified";
public static final String PATH_FIND = "find";
@@ -235,6 +242,16 @@ public class KeychainContract {
}
+ public static class UpdatedKeys implements UpdatedKeysColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_UPDATED_KEYS).build();
+
+ public static final String CONTENT_TYPE
+ = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.updated_keys";
+ public static final String CONTENT_ITEM_TYPE
+ = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.updated_keys";
+ }
+
public static class UserPackets implements UserPacketsColumns, BaseColumns {
public static final String VERIFIED = "verified";
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
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 ff661e494..d7fb738fc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
import org.sufficientlysecure.keychain.util.Log;
@@ -53,7 +54,7 @@ import java.io.IOException;
*/
public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db";
- private static final int DATABASE_VERSION = 9;
+ private static final int DATABASE_VERSION = 12;
static Boolean apgHack = false;
private Context mContext;
@@ -61,6 +62,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
String KEY_RINGS_PUBLIC = "keyrings_public";
String KEY_RINGS_SECRET = "keyrings_secret";
String KEYS = "keys";
+ String UPDATED_KEYS = "updated_keys";
String USER_PACKETS = "user_packets";
String CERTS = "certs";
String API_APPS = "api_apps";
@@ -144,6 +146,14 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE"
+ ")";
+ private static final String CREATE_UPDATE_KEYS =
+ "CREATE TABLE IF NOT EXISTS " + Tables.UPDATED_KEYS + " ("
+ + UpdatedKeysColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY, "
+ + UpdatedKeysColumns.LAST_UPDATED + " INTEGER, "
+ + "FOREIGN KEY(" + UpdatedKeysColumns.MASTER_KEY_ID + ") REFERENCES "
+ + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ + ")";
+
private static final String CREATE_API_APPS =
"CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " ("
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
@@ -179,7 +189,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
+ ")";
- KeychainDatabase(Context context) {
+ public KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
@@ -206,6 +216,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_KEYS);
db.execSQL(CREATE_USER_PACKETS);
db.execSQL(CREATE_CERTS);
+ db.execSQL(CREATE_UPDATE_KEYS);
db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ACCOUNTS);
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
@@ -272,6 +283,19 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("DROP TABLE IF EXISTS user_ids");
db.execSQL(CREATE_USER_PACKETS);
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");
+ case 12:
+ db.execSQL(CREATE_UPDATE_KEYS);
+ if (oldVersion == 10) {
+ // no consolidate if we are updating from 10, we're just here for
+ // the api_accounts fix and the new update keys table
+ return;
+ }
}
@@ -295,10 +319,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();
}
}
@@ -382,17 +407,22 @@ 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();
}
private static void copy(File in, File out) throws IOException {
FileInputStream is = new FileInputStream(in);
FileOutputStream os = new FileOutputStream(out);
- byte[] buf = new byte[512];
- while (is.available() > 0) {
- int count = is.read(buf, 0, 512);
- os.write(buf, 0, count);
+ try {
+ byte[] buf = new byte[512];
+ while (is.available() > 0) {
+ int count = is.read(buf, 0, 512);
+ os.write(buf, 0, count);
+ }
+ } finally {
+ is.close();
+ os.close();
}
}
@@ -409,6 +439,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()) {
@@ -423,6 +454,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
// DANGEROUS, use in test code ONLY!
public void clearDatabase() {
getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC);
+ getWritableDatabase().execSQL("delete from " + Tables.API_ACCOUNTS);
+ getWritableDatabase().execSQL("delete from " + Tables.API_ALLOWED_KEYS);
+ getWritableDatabase().execSQL("delete from " + Tables.API_APPS);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
index ecb26b56a..d722fa9e7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
@@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
@@ -75,6 +76,9 @@ public class KeychainProvider extends ContentProvider {
private static final int KEY_RINGS_FIND_BY_EMAIL = 400;
private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
+ private static final int UPDATED_KEYS = 500;
+ private static final int UPDATED_KEYS_SPECIFIC = 501;
+
protected UriMatcher mUriMatcher;
/**
@@ -192,6 +196,12 @@ public class KeychainProvider extends ContentProvider {
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
+ KeychainContract.PATH_ALLOWED_KEYS, API_ALLOWED_KEYS);
+ /**
+ * to access table containing last updated dates of keys
+ */
+ matcher.addURI(authority, KeychainContract.BASE_UPDATED_KEYS, UPDATED_KEYS);
+ matcher.addURI(authority, KeychainContract.BASE_UPDATED_KEYS + "/*", UPDATED_KEYS_SPECIFIC);
+
return matcher;
}
@@ -231,6 +241,11 @@ public class KeychainProvider extends ContentProvider {
case KEY_RING_SECRET:
return KeyRings.CONTENT_ITEM_TYPE;
+ case UPDATED_KEYS:
+ return UpdatedKeys.CONTENT_TYPE;
+ case UPDATED_KEYS_SPECIFIC:
+ return UpdatedKeys.CONTENT_ITEM_TYPE;
+
case API_APPS:
return ApiApps.CONTENT_TYPE;
@@ -536,7 +551,6 @@ public class KeychainProvider extends ContentProvider {
}
break;
-
}
case KEY_RINGS_PUBLIC:
@@ -631,23 +645,42 @@ public class KeychainProvider extends ContentProvider {
break;
}
- case API_APPS:
+ case UPDATED_KEYS:
+ case UPDATED_KEYS_SPECIFIC: {
+ HashMap<String, String> projectionMap = new HashMap<>();
+ qb.setTables(Tables.UPDATED_KEYS);
+ projectionMap.put(UpdatedKeys.MASTER_KEY_ID, Tables.UPDATED_KEYS + "."
+ + UpdatedKeys.MASTER_KEY_ID);
+ projectionMap.put(UpdatedKeys.LAST_UPDATED, Tables.UPDATED_KEYS + "."
+ + UpdatedKeys.LAST_UPDATED);
+ qb.setProjectionMap(projectionMap);
+ if (match == UPDATED_KEYS_SPECIFIC) {
+ qb.appendWhere(UpdatedKeys.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+ }
+ break;
+ }
+
+ case API_APPS: {
qb.setTables(Tables.API_APPS);
break;
- case API_APPS_BY_PACKAGE_NAME:
+ }
+ case API_APPS_BY_PACKAGE_NAME: {
qb.setTables(Tables.API_APPS);
qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
- case API_ACCOUNTS:
+ }
+ case API_ACCOUNTS: {
qb.setTables(Tables.API_ACCOUNTS);
qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
break;
- case API_ACCOUNTS_BY_ACCOUNT_NAME:
+ }
+ case API_ACCOUNTS_BY_ACCOUNT_NAME: {
qb.setTables(Tables.API_ACCOUNTS);
qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
@@ -656,14 +689,17 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
- case API_ALLOWED_KEYS:
+ }
+ case API_ALLOWED_KEYS: {
qb.setTables(Tables.API_ALLOWED_KEYS);
qb.appendWhere(Tables.API_ALLOWED_KEYS + "." + ApiAccounts.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
break;
- default:
+ }
+ default: {
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
+ }
}
@@ -708,47 +744,53 @@ public class KeychainProvider extends ContentProvider {
final int match = mUriMatcher.match(uri);
switch (match) {
- case KEY_RING_PUBLIC:
+ case KEY_RING_PUBLIC: {
db.insertOrThrow(Tables.KEY_RINGS_PUBLIC, null, values);
keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
break;
-
- case KEY_RING_SECRET:
+ }
+ case KEY_RING_SECRET: {
db.insertOrThrow(Tables.KEY_RINGS_SECRET, null, values);
keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
break;
-
- case KEY_RING_KEYS:
+ }
+ case KEY_RING_KEYS: {
db.insertOrThrow(Tables.KEYS, null, values);
keyId = values.getAsLong(Keys.MASTER_KEY_ID);
break;
-
- case KEY_RING_USER_IDS:
+ }
+ case KEY_RING_USER_IDS: {
// iff TYPE is null, user_id MUST be null as well
- if ( ! (values.get(UserPacketsColumns.TYPE) == null
+ if (!(values.get(UserPacketsColumns.TYPE) == null
? (values.get(UserPacketsColumns.USER_ID) != null && values.get(UserPacketsColumns.ATTRIBUTE_DATA) == null)
: (values.get(UserPacketsColumns.ATTRIBUTE_DATA) != null && values.get(UserPacketsColumns.USER_ID) == null)
- )) {
+ )) {
throw new AssertionError("Incorrect type for user packet! This is a bug!");
}
- if (values.get(UserPacketsColumns.RANK) == 0 && values.get(UserPacketsColumns.USER_ID) == null) {
+ if (((Number) values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) {
throw new AssertionError("Rank 0 user packet must be a user id!");
}
db.insertOrThrow(Tables.USER_PACKETS, null, values);
keyId = values.getAsLong(UserPackets.MASTER_KEY_ID);
break;
-
- case KEY_RING_CERTS:
+ }
+ case KEY_RING_CERTS: {
// we replace here, keeping only the latest signature
// TODO this would be better handled in savePublicKeyRing directly!
db.replaceOrThrow(Tables.CERTS, null, values);
keyId = values.getAsLong(Certs.MASTER_KEY_ID);
break;
-
- case API_APPS:
+ }
+ case UPDATED_KEYS: {
+ long updatedKeyId = db.replace(Tables.UPDATED_KEYS, null, values);
+ rowUri = UpdatedKeys.CONTENT_URI.buildUpon().appendPath("" + updatedKeyId)
+ .build();
+ break;
+ }
+ case API_APPS: {
db.insertOrThrow(Tables.API_APPS, null, values);
break;
-
+ }
case API_ACCOUNTS: {
// set foreign key automatically based on given uri
// e.g., api_apps/com.example.app/accounts/
@@ -767,8 +809,9 @@ public class KeychainProvider extends ContentProvider {
db.insertOrThrow(Tables.API_ALLOWED_KEYS, null, values);
break;
}
- default:
+ default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
}
if (keyId != null) {
@@ -826,20 +869,24 @@ public class KeychainProvider extends ContentProvider {
break;
}
- case API_APPS_BY_PACKAGE_NAME:
+ case API_APPS_BY_PACKAGE_NAME: {
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, additionalSelection),
selectionArgs);
break;
- case API_ACCOUNTS_BY_ACCOUNT_NAME:
+ }
+ case API_ACCOUNTS_BY_ACCOUNT_NAME: {
count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, additionalSelection),
selectionArgs);
break;
- case API_ALLOWED_KEYS:
+ }
+ case API_ALLOWED_KEYS: {
count = db.delete(Tables.API_ALLOWED_KEYS, buildDefaultApiAllowedKeysSelection(uri, additionalSelection),
selectionArgs);
break;
- default:
+ }
+ default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
}
// notify of changes in db
@@ -875,16 +922,19 @@ public class KeychainProvider extends ContentProvider {
count = db.update(Tables.KEYS, values, actualSelection, selectionArgs);
break;
}
- case API_APPS_BY_PACKAGE_NAME:
+ case API_APPS_BY_PACKAGE_NAME: {
count = db.update(Tables.API_APPS, values,
buildDefaultApiAppsSelection(uri, selection), selectionArgs);
break;
- case API_ACCOUNTS_BY_ACCOUNT_NAME:
+ }
+ case API_ACCOUNTS_BY_ACCOUNT_NAME: {
count = db.update(Tables.API_ACCOUNTS, values,
buildDefaultApiAccountsSelection(uri, selection), selectionArgs);
break;
- default:
+ }
+ default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
}
// notify of changes in db
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 bf56417e9..d9ef4f3c8 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;
@@ -38,7 +39,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
-import org.sufficientlysecure.keychain.operations.ImportExportOperation;
+import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
@@ -49,7 +50,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing;
-import org.sufficientlysecure.keychain.pgp.PgpConstants;
+import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
@@ -61,6 +62,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -81,6 +84,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* This class contains high level methods for database access. Despite its
@@ -648,10 +652,6 @@ public class ProviderHelper {
UserPacketItem item = uids.get(userIdRank);
operations.add(buildUserIdOperations(masterKeyId, item, userIdRank));
- if (item.selfCert == null) {
- throw new AssertionError("User ids MUST be self-certified at this point!!");
- }
-
if (item.selfRevocation != null) {
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfRevocation,
Certs.VERIFIED_SELF));
@@ -659,6 +659,10 @@ public class ProviderHelper {
continue;
}
+ if (item.selfCert == null) {
+ throw new AssertionError("User ids MUST be self-certified at this point!!");
+ }
+
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert,
selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF));
@@ -684,6 +688,36 @@ public class ProviderHelper {
mIndent -= 1;
}
+ // before deleting key, retrieve it's last updated time
+ final int INDEX_MASTER_KEY_ID = 0;
+ final int INDEX_LAST_UPDATED = 1;
+ Cursor lastUpdatedCursor = mContentResolver.query(
+ UpdatedKeys.CONTENT_URI,
+ new String[]{
+ UpdatedKeys.MASTER_KEY_ID,
+ UpdatedKeys.LAST_UPDATED
+ },
+ UpdatedKeys.MASTER_KEY_ID + " = ?",
+ new String[]{"" + masterKeyId},
+ null
+ );
+ if (lastUpdatedCursor.moveToNext()) {
+ // there was an entry to re-insert
+ // this operation must happen after the new key is inserted
+ ContentValues lastUpdatedEntry = new ContentValues(2);
+ lastUpdatedEntry.put(UpdatedKeys.MASTER_KEY_ID,
+ lastUpdatedCursor.getLong(INDEX_MASTER_KEY_ID));
+ lastUpdatedEntry.put(UpdatedKeys.LAST_UPDATED,
+ lastUpdatedCursor.getLong(INDEX_LAST_UPDATED));
+ operations.add(
+ ContentProviderOperation
+ .newInsert(UpdatedKeys.CONTENT_URI)
+ .withValues(lastUpdatedEntry)
+ .build()
+ );
+ }
+ lastUpdatedCursor.close();
+
try {
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
int deleted = mContentResolver.delete(
@@ -725,7 +759,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)) {
@@ -782,7 +816,7 @@ public class ProviderHelper {
// first, mark all keys as not available
ContentValues values = new ContentValues();
- values.put(Keys.HAS_SECRET, SecretKeyType.UNAVAILABLE.getNum());
+ values.put(Keys.HAS_SECRET, SecretKeyType.GNU_DUMMY.getNum());
mContentResolver.update(uri, values, null, null);
// then, mark exactly the keys we have available
@@ -831,7 +865,7 @@ public class ProviderHelper {
mIndent -= 1;
// this implicitly leaves all keys which were not in the secret key ring
- // with has_secret = 0
+ // with has_secret = 1
}
log(LogType.MSG_IS_SUCCESS);
@@ -906,7 +940,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 +1066,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 +1093,7 @@ public class ProviderHelper {
}
+ @NonNull
public ConsolidateResult consolidateDatabaseStep1(Progressable progress) {
OperationLog log = new OperationLog();
@@ -1082,7 +1119,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 +1161,7 @@ public class ProviderHelper {
}
});
+ cursor.close();
} catch (IOException e) {
Log.e(Constants.TAG, "error saving secret", e);
@@ -1143,7 +1181,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 +1223,7 @@ public class ProviderHelper {
}
});
+ cursor.close();
} catch (IOException e) {
Log.e(Constants.TAG, "error saving public", e);
@@ -1200,12 +1239,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) {
@@ -1231,6 +1272,28 @@ public class ProviderHelper {
}
// 2. wipe database (IT'S DANGEROUS)
+
+ // first, backup our list of updated key times
+ ArrayList<ContentValues> updatedKeysValues = new ArrayList<>();
+ final int INDEX_MASTER_KEY_ID = 0;
+ final int INDEX_LAST_UPDATED = 1;
+ Cursor lastUpdatedCursor = mContentResolver.query(
+ UpdatedKeys.CONTENT_URI,
+ new String[]{
+ UpdatedKeys.MASTER_KEY_ID,
+ UpdatedKeys.LAST_UPDATED
+ },
+ null, null, null);
+ while (lastUpdatedCursor.moveToNext()) {
+ ContentValues values = new ContentValues();
+ values.put(UpdatedKeys.MASTER_KEY_ID,
+ lastUpdatedCursor.getLong(INDEX_MASTER_KEY_ID));
+ values.put(UpdatedKeys.LAST_UPDATED,
+ lastUpdatedCursor.getLong(INDEX_LAST_UPDATED));
+ updatedKeysValues.add(values);
+ }
+ lastUpdatedCursor.close();
+
log.add(LogType.MSG_CON_DB_CLEAR, indent);
mContentResolver.delete(KeyRings.buildUnifiedKeyRingsUri(), null, null);
@@ -1248,9 +1311,9 @@ public class ProviderHelper {
// 3. Re-Import secret keyrings from cache
if (numSecrets > 0) {
- ImportKeyResult result = new ImportExportOperation(mContext, this,
+ ImportKeyResult result = new ImportOperation(mContext, this,
new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport))
- .importKeyRings(itSecrets, numSecrets, null);
+ .serialKeyRingImport(itSecrets, numSecrets, null, null);
log.add(result, indent);
} else {
log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent);
@@ -1276,10 +1339,14 @@ public class ProviderHelper {
// 4. Re-Import public keyrings from cache
if (numPublics > 0) {
- ImportKeyResult result = new ImportExportOperation(mContext, this,
+ ImportKeyResult result = new ImportOperation(mContext, this,
new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport))
- .importKeyRings(itPublics, numPublics, null);
+ .serialKeyRingImport(itPublics, numPublics, null, null);
log.add(result, indent);
+ // re-insert our backed up list of updated key times
+ // TODO: can this cause issues in case a public key re-import failed?
+ mContentResolver.bulkInsert(UpdatedKeys.CONTENT_URI,
+ updatedKeysValues.toArray(new ContentValues[updatedKeysValues.size()]));
} else {
log.add(LogType.MSG_CON_REIMPORT_PUBLIC_SKIP, indent);
}
@@ -1389,6 +1456,14 @@ public class ProviderHelper {
return getKeyRingAsArmoredString(data);
}
+ public Uri renewKeyLastUpdatedTime(long masterKeyId, long time, TimeUnit timeUnit) {
+ ContentValues values = new ContentValues();
+ values.put(UpdatedKeys.MASTER_KEY_ID, masterKeyId);
+ values.put(UpdatedKeys.LAST_UPDATED, timeUnit.toSeconds(time));
+
+ return mContentResolver.insert(UpdatedKeys.CONTENT_URI, values);
+ }
+
public ArrayList<String> getRegisteredApiApps() {
Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null);
@@ -1414,7 +1489,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;
}
@@ -1426,9 +1501,9 @@ public class ProviderHelper {
// DEPRECATED and thus hardcoded
values.put(KeychainContract.ApiAccounts.COMPRESSION, CompressionAlgorithmTags.ZLIB);
values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM,
- PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
+ PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT);
values.put(KeychainContract.ApiAccounts.HASH_ALORITHM,
- PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED);
+ PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT);
return values;
}
@@ -1460,7 +1535,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 {
@@ -1514,8 +1589,8 @@ public class ProviderHelper {
return keyIds;
}
- public Set<Long> getAllowedKeyIdsForApp(Uri uri) {
- Set<Long> keyIds = new HashSet<>();
+ public HashSet<Long> getAllowedKeyIdsForApp(Uri uri) {
+ HashSet<Long> keyIds = new HashSet<>();
Cursor cursor = mContentResolver.query(uri, null, null, null, null);
try {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
index 45f806960..7e9b24989 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
@@ -18,6 +18,8 @@
package org.sufficientlysecure.keychain.provider;
+
+import android.content.ClipDescription;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
@@ -38,6 +40,25 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;
+/**
+ * TemporaryStorageProvider stores decrypted files inside the app's cache directory previously to
+ * sharing them with other applications.
+ *
+ * Security:
+ * - It is writable by OpenKeychain only (see Manifest), but exported for reading files
+ * - It uses UUIDs as identifiers which makes predicting files from outside impossible
+ * - Querying a number of files is not allowed, only querying single files
+ * -> You can only open a file if you know the Uri containing the precise UUID, this Uri is only
+ * revealed when the user shares a decrypted file with another app.
+ *
+ * Why is support lib's FileProvider not used?
+ * Because granting Uri permissions temporarily does not work correctly. See
+ * - https://code.google.com/p/android/issues/detail?id=76683
+ * - https://github.com/nmr8acme/FileProvider-permission-bug
+ * - http://stackoverflow.com/q/24467696
+ * - http://stackoverflow.com/q/18249007
+ * - Comments at http://www.blogc.at/2014/03/23/share-private-files-with-other-apps-fileprovider/
+ */
public class TemporaryStorageProvider extends ContentProvider {
private static final String DB_NAME = "tempstorage.db";
@@ -45,17 +66,37 @@ public class TemporaryStorageProvider extends ContentProvider {
private static final String COLUMN_ID = "id";
private static final String COLUMN_NAME = "name";
private static final String COLUMN_TIME = "time";
- private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/");
- private static final int DB_VERSION = 2;
+ private static final String COLUMN_TYPE = "mimetype";
+ public static final String CONTENT_AUTHORITY = Constants.TEMPSTORAGE_AUTHORITY;
+ private static final Uri BASE_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
+ private static final int DB_VERSION = 3;
private static File cacheDir;
+ public static Uri createFile(Context context, String targetName, String mimeType) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(COLUMN_NAME, targetName);
+ contentValues.put(COLUMN_TYPE, mimeType);
+ return context.getContentResolver().insert(BASE_URI, contentValues);
+ }
+
public static Uri createFile(Context context, String targetName) {
ContentValues contentValues = new ContentValues();
contentValues.put(COLUMN_NAME, targetName);
return context.getContentResolver().insert(BASE_URI, contentValues);
}
+ public static Uri createFile(Context context) {
+ ContentValues contentValues = new ContentValues();
+ return context.getContentResolver().insert(BASE_URI, contentValues);
+ }
+
+ public static int setMimeType(Context context, Uri uri, String mimetype) {
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_TYPE, mimetype);
+ return context.getContentResolver().update(uri, values, null, null);
+ }
+
public static int cleanUp(Context context) {
return context.getContentResolver().delete(BASE_URI, COLUMN_TIME + "< ?",
new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)});
@@ -72,6 +113,7 @@ public class TemporaryStorageProvider extends ContentProvider {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" +
COLUMN_ID + " TEXT PRIMARY KEY, " +
COLUMN_NAME + " TEXT, " +
+ COLUMN_TYPE + " TEXT, " +
COLUMN_TIME + " INTEGER" +
");");
}
@@ -88,6 +130,8 @@ public class TemporaryStorageProvider extends ContentProvider {
COLUMN_NAME + " TEXT, " +
COLUMN_TIME + " INTEGER" +
");");
+ case 2:
+ db.execSQL("ALTER TABLE files ADD COLUMN " + COLUMN_TYPE + " TEXT");
}
}
}
@@ -115,6 +159,10 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ if (uri.getLastPathSegment() == null) {
+ throw new SecurityException("Listing temporary files is not allowed, only querying single files.");
+ }
+
File file;
try {
file = getFile(uri);
@@ -125,9 +173,15 @@ public class TemporaryStorageProvider extends ContentProvider {
new String[]{uri.getLastPathSegment()}, null, null, null);
if (fileName != null) {
if (fileName.moveToNext()) {
- MatrixCursor cursor =
- new MatrixCursor(new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, "_data"});
- cursor.newRow().add(fileName.getString(0)).add(file.length()).add(file.getAbsolutePath());
+ MatrixCursor cursor = new MatrixCursor(new String[]{
+ OpenableColumns.DISPLAY_NAME,
+ OpenableColumns.SIZE,
+ "_data"
+ });
+ cursor.newRow()
+ .add(fileName.getString(0))
+ .add(file.length())
+ .add(file.getAbsolutePath());
fileName.close();
return cursor;
}
@@ -138,9 +192,30 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public String getType(Uri uri) {
- // Note: If we can find a files mime type, we can decrypt it to temp storage and open it after
- // encryption. The mime type is needed, else UI really sucks and some apps break.
- return "*/*";
+ Cursor cursor = db.getReadableDatabase().query(TABLE_FILES,
+ new String[]{COLUMN_TYPE}, COLUMN_ID + "=?",
+ new String[]{uri.getLastPathSegment()}, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ if (!cursor.isNull(0)) {
+ return cursor.getString(0);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return "application/octet-stream";
+ }
+
+ @Override
+ public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+ String type = getType(uri);
+ if (ClipDescription.compareMimeTypes(type, mimeTypeFilter)) {
+ return new String[]{type};
+ }
+ return null;
}
@Override
@@ -151,9 +226,14 @@ public class TemporaryStorageProvider extends ContentProvider {
String uuid = UUID.randomUUID().toString();
values.put(COLUMN_ID, uuid);
int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values);
+ if (insert == -1) {
+ Log.e(Constants.TAG, "Insert failed!");
+ return null;
+ }
try {
getFile(uuid).createNewFile();
} catch (IOException e) {
+ Log.e(Constants.TAG, "File creation failed!");
return null;
}
return Uri.withAppendedPath(BASE_URI, uuid);
@@ -161,10 +241,13 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- if (uri.getLastPathSegment() != null) {
- selection = DatabaseUtil.concatenateWhere(selection, COLUMN_ID + "=?");
- selectionArgs = DatabaseUtil.appendSelectionArgs(selectionArgs, new String[]{uri.getLastPathSegment()});
+ if (uri == null || uri.getLastPathSegment() == null) {
+ return 0;
}
+
+ selection = DatabaseUtil.concatenateWhere(selection, COLUMN_ID + "=?");
+ selectionArgs = DatabaseUtil.appendSelectionArgs(selectionArgs, new String[]{uri.getLastPathSegment()});
+
Cursor files = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_ID}, selection,
selectionArgs, null, null, null);
if (files != null) {
@@ -179,11 +262,19 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException("Update not supported");
+ if (values.size() != 1 || !values.containsKey(COLUMN_TYPE)) {
+ throw new UnsupportedOperationException("Update supported only for type field!");
+ }
+ if (selection != null || selectionArgs != null) {
+ throw new UnsupportedOperationException("Update supported only for plain uri!");
+ }
+ return db.getWritableDatabase().update(TABLE_FILES, values,
+ COLUMN_ID + " = ?", new String[]{uri.getLastPathSegment()});
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
return openFileHelper(uri, mode);
}
+
}
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 4a8bf9332..49079f585 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2013-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
@@ -24,21 +24,22 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import android.text.TextUtils;
import org.openintents.openpgp.IOpenPgpService;
+import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
-import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
-import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
-import org.sufficientlysecure.keychain.pgp.PgpConstants;
-import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
+import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
@@ -64,7 +65,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
-import java.util.Set;
+import java.util.Date;
+import java.util.HashSet;
public class OpenPgpService extends RemoteService {
@@ -88,8 +90,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 +104,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 +138,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,9 +166,12 @@ 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:
case NFC_DECRYPT:
case NFC_SIGN: {
// build PendingIntent for YubiKey NFC operations
@@ -174,6 +179,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);
}
@@ -184,6 +190,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);
}
@@ -241,7 +248,7 @@ public class OpenPgpService extends RemoteService {
.setCleartextSignature(cleartextSign)
.setDetachedSignature(!cleartextSign)
.setVersionHeader(null)
- .setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED);
+ .setSignatureHashAlgorithm(PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT);
Intent signKeyIdIntent = getSignKeyMasterId(data);
// NOTE: Fallback to return account settings (Old API)
@@ -278,12 +285,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!
@@ -293,7 +300,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();
@@ -351,9 +359,9 @@ public class OpenPgpService extends RemoteService {
boolean enableCompression = data.getBooleanExtra(OpenPgpApi.EXTRA_ENABLE_COMPRESSION, true);
int compressionId;
if (enableCompression) {
- compressionId = CompressionAlgorithmTags.ZLIB;
+ compressionId = PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT;
} else {
- compressionId = CompressionAlgorithmTags.UNCOMPRESSED;
+ compressionId = PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED;
}
// first try to get key ids from non-ambiguous key id extra
@@ -383,8 +391,8 @@ public class OpenPgpService extends RemoteService {
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel();
pseInput.setEnableAsciiArmorOutput(asciiArmor)
.setVersionHeader(null)
- .setCompressionId(compressionId)
- .setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED)
+ .setCompressionAlgorithm(compressionId)
+ .setSymmetricEncryptionAlgorithm(PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT)
.setEncryptionMasterKeyIds(keyIds)
.setFailOnMissingEncryptionKeyIds(true);
@@ -413,7 +421,7 @@ public class OpenPgpService extends RemoteService {
}
// sign and encrypt
- pseInput.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED)
+ pseInput.setSignatureHashAlgorithm(PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT)
.setAdditionalEncryptId(signKeyId); // add sign key for encryption
}
@@ -433,12 +441,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);
@@ -448,7 +456,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();
@@ -488,23 +497,23 @@ public class OpenPgpService extends RemoteService {
}
}
- private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input,
+ private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor inputDescriptor,
ParcelFileDescriptor output, boolean decryptMetadataOnly) {
- InputStream is = null;
- OutputStream os = null;
+ InputStream inputStream = null;
+ OutputStream outputStream = null;
try {
// Get Input- and OutputStream from ParcelFileDescriptor
- is = new ParcelFileDescriptor.AutoCloseInputStream(input);
+ inputStream = new ParcelFileDescriptor.AutoCloseInputStream(inputDescriptor);
// output is optional, e.g., for verifying detached signatures
if (decryptMetadataOnly || output == null) {
- os = null;
+ outputStream = null;
} else {
- os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
+ outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(output);
}
String currentPkg = getCurrentCallingPackage();
- Set<Long> allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp(
+ HashSet<Long> allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp(
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
@@ -512,38 +521,38 @@ public class OpenPgpService extends RemoteService {
ApiAccounts.buildBaseUri(currentPkg)));
}
- long inputLength = is.available();
- InputData inputData = new InputData(is, inputLength);
-
- PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
- this, new ProviderHelper(getContext()), null, inputData, os
- );
-
- CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
- if (inputParcel == null) {
- inputParcel = new CryptoInputParcel();
+ CryptoInputParcel cryptoInput = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
+ if (cryptoInput == null) {
+ cryptoInput = new CryptoInputParcel();
}
// 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)));
+ cryptoInput.mPassphrase =
+ new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
}
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
+ PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mProviderHelper, null);
+
+ long inputLength = inputStream.available();
+ InputData inputData = new InputData(inputStream, inputLength);
+
// allow only private keys associated with accounts of this app
// no support for symmetric encryption
- builder.setAllowSymmetricDecryption(false)
+ PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel()
+ .setAllowSymmetricDecryption(false)
.setAllowedKeyIds(allowedKeyIds)
.setDecryptMetadataOnly(decryptMetadataOnly)
.setDetachedSignature(detachedSignature);
- DecryptVerifyResult pgpResult = builder.build().execute(inputParcel);
+ DecryptVerifyResult pgpResult = op.execute(input, cryptoInput, inputData, outputStream);
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);
@@ -554,40 +563,71 @@ public class OpenPgpService extends RemoteService {
Intent result = new Intent();
OpenPgpSignatureResult signatureResult = pgpResult.getSignatureResult();
- // TODO: currently RESULT_TYPE_UNENCRYPTED_UNSIGNED is never returned
- // instead an error is returned when no pgp data has been found
- int resultType = OpenPgpApi.RESULT_TYPE_UNENCRYPTED_UNSIGNED;
- if (signatureResult != null) {
- resultType |= OpenPgpApi.RESULT_TYPE_SIGNED;
- if (!signatureResult.isSignatureOnly()) {
- resultType |= OpenPgpApi.RESULT_TYPE_ENCRYPTED;
+
+ result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult);
+
+ if (signatureResult.getResult() == OpenPgpSignatureResult.RESULT_KEY_MISSING) {
+ // If signature is unknown we return an _additional_ PendingIntent
+ // to retrieve the missing key
+ result.putExtra(OpenPgpApi.RESULT_INTENT, getKeyserverPendingIntent(data, signatureResult.getKeyId()));
+ } else {
+ // If signature key is known, return PendingIntent to show key
+ result.putExtra(OpenPgpApi.RESULT_INTENT, getShowKeyPendingIntent(signatureResult.getKeyId()));
+ }
+
+ if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 5) {
+ // RESULT_INVALID_KEY_REVOKED and RESULT_INVALID_KEY_EXPIRED have been added in version 5
+ if (signatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_REVOKED
+ || signatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED) {
+ signatureResult.setResult(OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE);
+ }
+ }
+
+ if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 8) {
+ // RESULT_INVALID_INSECURE has been added in version 8, fallback to RESULT_INVALID_SIGNATURE
+ if (signatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_INSECURE) {
+ signatureResult.setResult(OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE);
+ }
+
+ // RESULT_NO_SIGNATURE has been added in version 8, before the signatureResult was null
+ if (signatureResult.getResult() == OpenPgpSignatureResult.RESULT_NO_SIGNATURE) {
+ result.putExtra(OpenPgpApi.RESULT_SIGNATURE, (Parcelable[]) null);
}
- result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult);
+ // OpenPgpDecryptionResult does not exist in API < 8
+ {
+ OpenPgpDecryptionResult decryptionResult = pgpResult.getDecryptionResult();
- if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 5) {
- // SIGNATURE_KEY_REVOKED and SIGNATURE_KEY_EXPIRED have been added in version 5
- if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED
- || signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED) {
- signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_ERROR);
+ // case RESULT_NOT_ENCRYPTED, but a signature, fallback to deprecated signatureOnly variable
+ if (decryptionResult.getResult() == OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED
+ && signatureResult.getResult() != OpenPgpSignatureResult.RESULT_NO_SIGNATURE) {
+ signatureResult.setSignatureOnly(true);
}
+
+ // case RESULT_INSECURE, fallback to an error
+ if (decryptionResult.getResult() == OpenPgpDecryptionResult.RESULT_INSECURE) {
+ Intent resultError = new Intent();
+ resultError.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR,
+ "Insecure encryption: An outdated algorithm has been used!"));
+ resultError.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ return resultError;
+ }
+
+ // case RESULT_ENCRYPTED
+ // nothing to do!
}
+ }
- if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) {
- // If signature is unknown we return an _additional_ PendingIntent
- // to retrieve the missing key
- result.putExtra(OpenPgpApi.RESULT_INTENT, getKeyserverPendingIntent(data, signatureResult.getKeyId()));
- } else {
- // If signature key is known, return PendingIntent to show key
- result.putExtra(OpenPgpApi.RESULT_INTENT, getShowKeyPendingIntent(signatureResult.getKeyId()));
+ if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) >= 8) {
+ OpenPgpDecryptionResult decryptionResult = pgpResult.getDecryptionResult();
+ if (decryptionResult != null) {
+ result.putExtra(OpenPgpApi.RESULT_DECRYPTION, decryptionResult);
}
- } else {
- resultType |= OpenPgpApi.RESULT_TYPE_ENCRYPTED;
}
- result.putExtra(OpenPgpApi.RESULT_TYPE, resultType);
+
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) >= 4) {
- OpenPgpMetadata metadata = pgpResult.getDecryptMetadata();
+ OpenPgpMetadata metadata = pgpResult.getDecryptionMetadata();
if (metadata != null) {
result.putExtra(OpenPgpApi.RESULT_METADATA, metadata);
}
@@ -601,9 +641,8 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} else {
- LogEntryParcel errorMsg = pgpResult.getLog().getLast();
-
- if (errorMsg.mType == OperationResult.LogType.MSG_DC_ERROR_NO_KEY) {
+ //
+ if (pgpResult.isKeysDisallowed()) {
// allow user to select allowed keys
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_INTENT, getSelectAllowedKeysIntent(data));
@@ -611,32 +650,36 @@ public class OpenPgpService extends RemoteService {
return result;
}
- throw new Exception(getString(errorMsg.mType.getMsgId()));
+ String errorMsg = getString(pgpResult.getLog().getLast().mType.getMsgId());
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, errorMsg));
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ return result;
}
- } catch (Exception e) {
- Log.d(Constants.TAG, "decryptAndVerifyImpl", e);
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "decryptAndVerifyImpl", e);
Intent result = new Intent();
- result.putExtra(OpenPgpApi.RESULT_ERROR,
- new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
+ result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
} finally {
- if (is != null) {
+ if (inputStream != null) {
try {
- is.close();
+ inputStream.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing InputStream", e);
}
}
- if (os != null) {
+ if (outputStream != null) {
try {
- os.close();
+ outputStream.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing OutputStream", e);
}
}
}
+
}
private Intent getKeyImpl(Intent data) {
@@ -672,28 +715,40 @@ public class OpenPgpService extends RemoteService {
}
private Intent getSignKeyIdImpl(Intent data) {
- String preferredUserId = data.getStringExtra(OpenPgpApi.EXTRA_USER_ID);
+ // if data already contains EXTRA_SIGN_KEY_ID, it has been executed again
+ // after user interaction. Then, we just need to return the long again!
+ if (data.hasExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
+ long signKeyId = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID,
+ Constants.key.none);
- Intent intent = new Intent(getBaseContext(), SelectSignKeyIdActivity.class);
- String currentPkg = getCurrentCallingPackage();
- intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(currentPkg));
- intent.putExtra(SelectSignKeyIdActivity.EXTRA_USER_ID, preferredUserId);
- intent.putExtra(SelectSignKeyIdActivity.EXTRA_DATA, data);
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, signKeyId);
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
+ return result;
+ } else {
+ String preferredUserId = data.getStringExtra(OpenPgpApi.EXTRA_USER_ID);
- PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
- intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
+ Intent intent = new Intent(getBaseContext(), SelectSignKeyIdActivity.class);
+ String currentPkg = getCurrentCallingPackage();
+ intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(currentPkg));
+ intent.putExtra(SelectSignKeyIdActivity.EXTRA_USER_ID, preferredUserId);
+ intent.putExtra(SelectSignKeyIdActivity.EXTRA_DATA, data);
+
+ PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
- // return PendingIntent to be executed by client
- Intent result = new Intent();
- result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
- result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
+ // return PendingIntent to be executed by client
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
+ result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
- return result;
+ return result;
+ }
}
private Intent getKeyIdsImpl(Intent data) {
- // if data already contains key ids extra GET_KEY_IDS has been executed again
+ // if data already contains EXTRA_KEY_IDS, it has been executed again
// after user interaction. Then, we just need to return the array again!
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
long[] keyIdsArray = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
@@ -763,12 +818,13 @@ public class OpenPgpService extends RemoteService {
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 4
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 5
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 6
- && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 7) {
+ && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 7
+ && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 8) {
Intent result = new Intent();
OpenPgpError error = new OpenPgpError
(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n"
+ "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n"
- + "supported API versions: 3-7");
+ + "supported API versions: 3-8");
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java
index e4d4ac49a..792a4d253 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java
@@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.remote;
+import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
@@ -65,12 +66,11 @@ public abstract class RemoteService extends Service {
/**
* Checks if caller is allowed to access the API
*
- * @param data
* @return null if caller is allowed, or a Bundle with a PendingIntent
*/
protected Intent isAllowed(Intent data) {
try {
- if (isCallerAllowed(false)) {
+ if (isCallerAllowed()) {
return null;
} else {
String packageName = getCurrentCallingPackage();
@@ -130,8 +130,8 @@ public abstract class RemoteService extends Service {
}
private byte[] getPackageCertificate(String packageName) throws NameNotFoundException {
- PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
- PackageManager.GET_SIGNATURES);
+ @SuppressLint("PackageManagerGetSignatures") // we do check the byte array of *all* signatures
+ PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
// NOTE: Silly Android API naming: Signatures are actually certificates
Signature[] certificates = pkgInfo.signatures;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
@@ -211,22 +211,15 @@ public abstract class RemoteService extends Service {
* Checks if process that binds to this service (i.e. the package name corresponding to the
* process) is in the list of allowed package names.
*
- * @param allowOnlySelf allow only Keychain app itself
* @return true if process is allowed to use this service
* @throws WrongPackageCertificateException
*/
- private boolean isCallerAllowed(boolean allowOnlySelf) throws WrongPackageCertificateException {
- return isUidAllowed(Binder.getCallingUid(), allowOnlySelf);
+ private boolean isCallerAllowed() throws WrongPackageCertificateException {
+ return isUidAllowed(Binder.getCallingUid());
}
- private boolean isUidAllowed(int uid, boolean allowOnlySelf)
+ private boolean isUidAllowed(int uid)
throws WrongPackageCertificateException {
- if (android.os.Process.myUid() == uid) {
- return true;
- }
- if (allowOnlySelf) { // barrier
- return false;
- }
String[] callingPackages = getPackageManager().getPackagesForUid(uid);
@@ -237,7 +230,7 @@ public abstract class RemoteService extends Service {
}
}
- Log.d(Constants.TAG, "Uid is NOT allowed!");
+ Log.e(Constants.TAG, "Uid is NOT allowed!");
return false;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java
index 81181d61d..18afd2f23 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java
@@ -58,7 +58,7 @@ public class AccountSettingsFragment extends Fragment {
this.mAccSettings = accountSettings;
mAccNameView.setText(accountSettings.getAccountName());
- mSelectKeySpinner.setSelectedKeyId(accountSettings.getKeyId());
+ mSelectKeySpinner.setPreSelectedKeyId(accountSettings.getKeyId());
}
/**
@@ -107,7 +107,7 @@ public class AccountSettingsFragment extends Fragment {
if (resultCode == Activity.RESULT_OK) {
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
EditKeyResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
- mSelectKeySpinner.setSelectedKeyId(result.mMasterKeyId);
+ mSelectKeySpinner.setPreSelectedKeyId(result.mMasterKeyId);
} else {
Log.e(Constants.TAG, "missing result!");
}
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 2b71d6dc1..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");
}
@@ -217,13 +217,15 @@ public class AppSettingsActivity extends BaseActivity {
// show accounts only if available (deprecated API)
Cursor cursor = getContentResolver().query(accountsUri, null, null, null, null);
- if (cursor.moveToFirst()) {
+ if (cursor != null && cursor.moveToFirst()) try {
mAccountsLabel.setVisibility(View.VISIBLE);
mAccountsListFragment = AccountsListFragment.newInstance(accountsUri);
// Create an instance of the fragments
getSupportFragmentManager().beginTransaction()
.replace(R.id.api_accounts_list_fragment, mAccountsListFragment)
.commitAllowingStateLoss();
+ } finally {
+ cursor.close();
}
// Create an instance of the fragments
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java
index b880525ca..caa173f03 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java
@@ -17,10 +17,11 @@
package org.sufficientlysecure.keychain.remote.ui;
-import android.content.Context;
+
+import java.util.Set;
+
import android.content.OperationApplicationException;
import android.database.Cursor;
-import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
@@ -35,23 +36,17 @@ import android.widget.ListView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
-import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter;
+import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
+import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter;
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
import org.sufficientlysecure.keychain.util.Log;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.Vector;
-
public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String ARG_DATA_URI = "uri";
- private SelectKeyCursorAdapter mAdapter;
- private Set<Long> mSelectedMasterKeyIds;
+ private KeySelectableAdapter mAdapter;
private ProviderHelper mProviderHelper;
private Uri mDataUri;
@@ -80,8 +75,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- View layout = super.onCreateView(inflater, container,
- savedInstanceState);
+ View layout = super.onCreateView(inflater, container, savedInstanceState);
ListView lv = (ListView) layout.findViewById(android.R.id.list);
ViewGroup parent = (ViewGroup) lv.getParent();
@@ -109,67 +103,29 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
mDataUri = getArguments().getParcelable(ARG_DATA_URI);
- getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
-
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText(getString(R.string.list_empty));
- mAdapter = new SecretKeyCursorAdapter(getActivity(), null, 0, getListView());
-
+ Set<Long> checked = mProviderHelper.getAllKeyIdsForApp(mDataUri);
+ mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked);
setListAdapter(mAdapter);
+ getListView().setOnItemClickListener(mAdapter);
// Start out with a progress indicator.
setListShown(false);
- mSelectedMasterKeyIds = mProviderHelper.getAllKeyIdsForApp(mDataUri);
- Log.d(Constants.TAG, "allowed: " + mSelectedMasterKeyIds.toString());
-
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
- }
- /**
- * Selects items based on master key ids in list view
- *
- * @param masterKeyIds
- */
- private void preselectMasterKeyIds(Set<Long> masterKeyIds) {
- for (int i = 0; i < getListView().getCount(); ++i) {
- long listKeyId = mAdapter.getMasterKeyId(i);
- for (long keyId : masterKeyIds) {
- if (listKeyId == keyId) {
- getListView().setItemChecked(i, true);
- break;
- }
- }
- }
}
-
- /**
- * Returns all selected master key ids
- *
- * @return
- */
+ /** Returns all selected master key ids. */
public Set<Long> getSelectedMasterKeyIds() {
- // mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key
- // ids!
- Set<Long> keyIds = new HashSet<>();
- for (int i = 0; i < getListView().getCount(); ++i) {
- if (getListView().isItemChecked(i)) {
- keyIds.add(mAdapter.getMasterKeyId(i));
- }
- }
-
- return keyIds;
+ return mAdapter.getSelectedMasterKeyIds();
}
- /**
- * Returns all selected user ids
- *
- * @return
- */
+ /** Returns all selected user ids.
public String[] getSelectedUserIds() {
Vector<String> userIds = new Vector<>();
for (int i = 0; i < getListView().getCount(); ++i) {
@@ -181,7 +137,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
// make empty array to not return null
String userIdArray[] = new String[0];
return userIds.toArray(userIdArray);
- }
+ } */
public void saveAllowedKeys() {
try {
@@ -192,46 +148,11 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
}
@Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
-
- // These are the rows that we will retrieve.
- String[] projection = new String[]{
- KeyRings._ID,
- KeyRings.MASTER_KEY_ID,
- KeyRings.USER_ID,
- KeyRings.IS_EXPIRED,
- KeyRings.IS_REVOKED,
- KeyRings.HAS_ENCRYPT,
- KeyRings.VERIFIED,
- KeyRings.HAS_ANY_SECRET,
- KeyRings.HAS_DUPLICATE_USER_ID,
- KeyRings.CREATION,
- };
-
- String inMasterKeyList = null;
- if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.size() > 0) {
- inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN (";
- Iterator iter = mSelectedMasterKeyIds.iterator();
- while (iter.hasNext()) {
- inMasterKeyList += DatabaseUtils.sqlEscapeString("" + iter.next());
- if (iter.hasNext()) {
- inMasterKeyList += ", ";
- }
- }
- inMasterKeyList += ")";
- }
-
- String selection = KeyRings.HAS_ANY_SECRET + " != 0";
+ public Loader<Cursor> onCreateLoader(int loaderId, Bundle data) {
+ Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
+ String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1";
- String orderBy = KeyRings.USER_ID + " ASC";
- if (inMasterKeyList != null) {
- // sort by selected master keys
- orderBy = inMasterKeyList + " DESC, " + orderBy;
- }
- // Now create and return a CursorLoader that will take care of
- // creating a Cursor for the data being displayed.
- return new CursorLoader(getActivity(), baseUri, projection, selection, null, orderBy);
+ return new CursorLoader(getActivity(), baseUri, KeyAdapter.PROJECTION, where, null, null);
}
@Override
@@ -246,9 +167,6 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
} else {
setListShownNoAnimation(true);
}
-
- // preselect given master keys
- preselectMasterKeyIds(mSelectedMasterKeyIds);
}
@Override
@@ -259,36 +177,4 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
mAdapter.swapCursor(null);
}
- private class SecretKeyCursorAdapter extends SelectKeyCursorAdapter {
-
- public SecretKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) {
- super(context, c, flags, listView);
- }
-
- @Override
- protected void initIndex(Cursor cursor) {
- super.initIndex(cursor);
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- super.bindView(view, context, cursor);
- ViewHolderItem h = (ViewHolderItem) view.getTag();
-
- // We care about the checkbox
- h.selected.setVisibility(View.VISIBLE);
- // the getListView works because this is not a static subclass!
- h.selected.setChecked(getListView().isItemChecked(cursor.getPosition()));
-
- boolean enabled = false;
- if ((Boolean) h.statusIcon.getTag()) {
- h.statusIcon.setVisibility(View.GONE);
- enabled = true;
- }
-
- h.setEnabled(enabled);
- }
-
- }
-
}
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/remote/ui/SelectSignKeyIdActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java
index cb9f46f7f..bed49a6f6 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java
@@ -40,14 +40,9 @@ public class SelectSignKeyIdActivity extends BaseActivity {
private static final int REQUEST_CODE_CREATE_KEY = 0x00008884;
- private Uri mAppUri;
private String mPreferredUserId;
private Intent mData;
- private SelectSignKeyIdListFragment mListFragment;
- private TextView mActionCreateKey;
- private TextView mNone;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -62,15 +57,15 @@ public class SelectSignKeyIdActivity extends BaseActivity {
}
});
- mActionCreateKey = (TextView) findViewById(R.id.api_select_sign_key_create_key);
- mActionCreateKey.setOnClickListener(new View.OnClickListener() {
+ TextView createKeyButton = (TextView) findViewById(R.id.api_select_sign_key_create_key);
+ createKeyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
createKey(mPreferredUserId);
}
});
- mNone = (TextView) findViewById(R.id.api_select_sign_key_none);
- mNone.setOnClickListener(new View.OnClickListener() {
+ TextView noneButton = (TextView) findViewById(R.id.api_select_sign_key_none);
+ noneButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 0 is "none"
@@ -82,16 +77,16 @@ public class SelectSignKeyIdActivity extends BaseActivity {
});
Intent intent = getIntent();
- mAppUri = intent.getData();
+ Uri appUri = intent.getData();
mPreferredUserId = intent.getStringExtra(EXTRA_USER_ID);
mData = intent.getParcelableExtra(EXTRA_DATA);
- if (mAppUri == null) {
+ if (appUri == null) {
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
finish();
return;
} else {
- Log.d(Constants.TAG, "uri: " + mAppUri);
- startListFragments(savedInstanceState, mAppUri, mData);
+ Log.d(Constants.TAG, "uri: " + appUri);
+ startListFragments(savedInstanceState, appUri, mData);
}
}
@@ -113,11 +108,11 @@ public class SelectSignKeyIdActivity extends BaseActivity {
}
// Create an instance of the fragments
- mListFragment = SelectSignKeyIdListFragment.newInstance(dataUri, data);
+ SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment.newInstance(dataUri, data);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
- .replace(R.id.api_select_sign_key_list_fragment, mListFragment)
+ .replace(R.id.api_select_sign_key_list_fragment, listFragment)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
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 a7571a7ac..3cdbca633 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java
@@ -22,14 +22,13 @@ import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
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;
/**
@@ -44,6 +43,8 @@ public class CertifyActionsParcel implements Parcelable {
public ArrayList<CertifyAction> mCertifyActions = new ArrayList<>();
+ public String keyServerUri;
+
public CertifyActionsParcel(long masterKeyId) {
mMasterKeyId = masterKeyId;
mLevel = CertifyLevel.DEFAULT;
@@ -53,6 +54,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();
}
@@ -65,6 +67,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);
}
@@ -86,8 +89,7 @@ public class CertifyActionsParcel implements Parcelable {
final public ArrayList<String> mUserIds;
final public ArrayList<WrappedUserAttribute> mUserAttributes;
- public CertifyAction(long masterKeyId, List<String> userIds,
- List<WrappedUserAttribute> attributes) {
+ public CertifyAction(long masterKeyId, List<String> userIds, List<WrappedUserAttribute> attributes) {
mMasterKeyId = masterKeyId;
mUserIds = userIds == null ? null : new ArrayList<>(userIds);
mUserAttributes = attributes == null ? null : new ArrayList<>(attributes);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CloudImportService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CloudImportService.java
deleted file mode 100644
index 249586f6d..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CloudImportService.java
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package org.sufficientlysecure.keychain.service;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
-import org.sufficientlysecure.keychain.operations.ImportExportOperation;
-import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
-import org.sufficientlysecure.keychain.operations.results.OperationResult;
-import org.sufficientlysecure.keychain.pgp.Progressable;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.util.Log;
-import org.sufficientlysecure.keychain.util.ParcelableFileCache;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Iterator;
-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;
-
-/**
- * When this service is started it will initiate a multi-threaded key import and when done it will
- * shut itself down.
- */
-public class CloudImportService extends Service implements Progressable {
-
- // required as extras from intent
- public static final String EXTRA_MESSENGER = "messenger";
- public static final String EXTRA_DATA = "data";
-
- // required by data bundle
- public static final String IMPORT_KEY_LIST = "import_key_list";
- public static final String IMPORT_KEY_SERVER = "import_key_server";
-
- // indicates a request to cancel the import
- public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL";
-
- // tells the spawned threads whether the user has requested a cancel
- private static AtomicBoolean mActionCancelled = new AtomicBoolean(false);
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- /**
- * Used to accumulate the results of individual key imports
- */
- private class KeyImportAccumulator {
- private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
- private int mTotalKeys;
- private int mImportedKeys = 0;
- private Progressable mImportProgressable;
- ArrayList<Long> mImportedMasterKeyIds = new ArrayList<Long>();
- private int mBadKeys = 0;
- private int mNewKeys = 0;
- private int mUpdatedKeys = 0;
- private int mSecret = 0;
- private int mResultType = 0;
-
- public KeyImportAccumulator(int totalKeys) {
- mTotalKeys = totalKeys;
- // ignore updates from ImportExportOperation for now
- mImportProgressable = new Progressable() {
- @Override
- public void setProgress(String message, int current, int total) {
-
- }
-
- @Override
- public void setProgress(int resourceId, int current, int total) {
-
- }
-
- @Override
- public void setProgress(int current, int total) {
-
- }
-
- @Override
- public void setPreventCancel() {
-
- }
- };
- }
-
- public Progressable getImportProgressable() {
- return mImportProgressable;
- }
-
- public int getTotalKeys() {
- return mTotalKeys;
- }
-
- public int getImportedKeys() {
- return mImportedKeys;
- }
-
- public synchronized void accumulateKeyImport(ImportKeyResult result) {
- mImportedKeys++;
- mImportLog.addAll(result.getLog().toList());//accumulates log
- mBadKeys += result.mBadKeys;
- mNewKeys += result.mNewKeys;
- mUpdatedKeys += result.mUpdatedKeys;
- mSecret += result.mSecret;
-
- long[] masterKeyIds = result.getImportedMasterKeyIds();
- for (long masterKeyId : masterKeyIds) {
- mImportedMasterKeyIds.add(masterKeyId);
- }
-
- // if any key import has been cancelled, set result type to cancelled
- // resultType is added to in getConsolidatedKayImport to account for remaining factors
- mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
- }
-
- /**
- * returns accumulated result of all imports so far
- */
- public ImportKeyResult getConsolidatedImportKeyResult() {
-
- // adding required information to mResultType
- // special case,no keys requested for import
- if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) {
- mResultType = ImportKeyResult.RESULT_FAIL_NOTHING;
- } else {
- if (mNewKeys > 0) {
- mResultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
- }
- if (mUpdatedKeys > 0) {
- mResultType |= ImportKeyResult.RESULT_OK_UPDATED;
- }
- if (mBadKeys > 0) {
- mResultType |= ImportKeyResult.RESULT_WITH_ERRORS;
- if (mNewKeys == 0 && mUpdatedKeys == 0) {
- mResultType |= ImportKeyResult.RESULT_ERROR;
- }
- }
- if (mImportLog.containsWarnings()) {
- mResultType |= ImportKeyResult.RESULT_WARNINGS;
- }
- }
-
- long masterKeyIds[] = new long[mImportedMasterKeyIds.size()];
- for (int i = 0; i < masterKeyIds.length; i++) {
- masterKeyIds[i] = mImportedMasterKeyIds.get(i);
- }
-
- return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys,
- mSecret, masterKeyIds);
- }
-
- public boolean isImportFinished() {
- return mTotalKeys == mImportedKeys;
- }
- }
-
- private KeyImportAccumulator mKeyImportAccumulator;
-
- Messenger mMessenger;
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
-
- if (ACTION_CANCEL.equals(intent.getAction())) {
- mActionCancelled.set(true);
- return Service.START_NOT_STICKY;
- }
-
- mActionCancelled.set(false);//we haven't been cancelled, yet
-
- Bundle extras = intent.getExtras();
-
- mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
-
- Bundle data = extras.getBundle(EXTRA_DATA);
-
- final String keyServer = data.getString(IMPORT_KEY_SERVER);
- // keyList being null (in case key list to be reaad from cache) is checked by importKeys
- final ArrayList<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST);
-
- // Adding keys to the ThreadPoolExecutor takes time, we don't want to block the main thread
- Thread baseImportThread = new Thread(new Runnable() {
-
- @Override
- public void run() {
- importKeys(keyList, keyServer);
- }
- });
- baseImportThread.start();
- return Service.START_NOT_STICKY;
- }
-
- public void importKeys(ArrayList<ParcelableKeyRing> keyList, final String keyServer) {
- ParcelableFileCache<ParcelableKeyRing> cache =
- new ParcelableFileCache<>(this, "key_import.pcl");
- int totKeys = 0;
- Iterator<ParcelableKeyRing> keyListIterator = null;
- // either keyList or cache must be null, no guarantees otherwise
- if (keyList == null) {//export from cache, copied from ImportExportOperation.importKeyRings
-
- try {
- ParcelableFileCache.IteratorWithSize<ParcelableKeyRing> it = cache.readCache();
- keyListIterator = it;
- totKeys = it.getSize();
- } catch (IOException e) {
-
- // Special treatment here, we need a lot
- OperationResult.OperationLog log = new OperationResult.OperationLog();
- log.add(OperationResult.LogType.MSG_IMPORT, 0, 0);
- log.add(OperationResult.LogType.MSG_IMPORT_ERROR_IO, 0, 0);
-
- keyImportFailed(new ImportKeyResult(ImportKeyResult.RESULT_ERROR, log));
- }
- } else {
- keyListIterator = keyList.iterator();
- totKeys = keyList.size();
- }
-
-
- if (keyListIterator != null) {
- mKeyImportAccumulator = new KeyImportAccumulator(totKeys);
- setProgress(0, totKeys);
-
- final int maxThreads = 200;
- ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads,
- 30L, TimeUnit.SECONDS,
- new SynchronousQueue<Runnable>());
-
- while (keyListIterator.hasNext()) {
-
- final ParcelableKeyRing pkRing = keyListIterator.next();
-
- Runnable importOperationRunnable = new Runnable() {
-
- @Override
- public void run() {
- ImportKeyResult result = null;
- try {
- ImportExportOperation importExportOperation = new ImportExportOperation(
- CloudImportService.this,
- new ProviderHelper(CloudImportService.this),
- mKeyImportAccumulator.getImportProgressable(),
- mActionCancelled);
-
- ArrayList<ParcelableKeyRing> list = new ArrayList<>();
- list.add(pkRing);
- result = importExportOperation.importKeyRings(list,
- keyServer);
- } finally {
- // in the off-chance that importKeyRings does something to crash the
- // thread before it can call singleKeyRingImportCompleted, our imported
- // key count will go wrong. This will cause the service to never die,
- // and the progress dialog to stay displayed. The finally block was
- // originally meant to ensure singleKeyRingImportCompleted was called,
- // and checks for null were to be introduced, but in such a scenario,
- // knowing an uncaught error exists in importKeyRings is more important.
-
- // if a null gets passed, something wrong is happening. We want a crash.
-
- singleKeyRingImportCompleted(result);
- }
- }
- };
-
- importExecutor.execute(importOperationRunnable);
- }
- }
- }
-
- private synchronized void singleKeyRingImportCompleted(ImportKeyResult result) {
- // increase imported key count and accumulate log and bad, new etc. key counts from result
- mKeyImportAccumulator.accumulateKeyImport(result);
-
- setProgress(mKeyImportAccumulator.getImportedKeys(), mKeyImportAccumulator.getTotalKeys());
-
- if (mKeyImportAccumulator.isImportFinished()) {
- ContactSyncAdapterService.requestSync();
-
- sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY,
- mKeyImportAccumulator.getConsolidatedImportKeyResult());
-
- stopSelf();//we're done here
- }
- }
-
- private void keyImportFailed(ImportKeyResult result) {
- sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY, result);
- }
-
- private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, Integer arg2, Bundle data) {
-
- Message msg = Message.obtain();
- assert msg != null;
- msg.arg1 = status.ordinal();
- if (arg2 != null) {
- msg.arg2 = arg2;
- }
- if (data != null) {
- msg.setData(data);
- }
-
- try {
- mMessenger.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);
- }
- }
-
- private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, OperationResult data) {
- Bundle bundle = new Bundle();
- bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
- sendMessageToHandler(status, null, bundle);
- }
-
- private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, Bundle data) {
- sendMessageToHandler(status, null, data);
- }
-
- private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status) {
- sendMessageToHandler(status, null, null);
- }
-
- /**
- * Set progress of ProgressDialog by sending message to handler on UI thread
- */
- @Override
- public synchronized void setProgress(String message, int progress, int max) {
- Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
- + max);
-
- Bundle data = new Bundle();
- if (message != null) {
- data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
- }
- data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
- data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
-
- sendMessageToHandler(ServiceProgressHandler.MessageStatus.UPDATE_PROGRESS, null, data);
- }
-
- @Override
- public synchronized void setProgress(int resourceId, int progress, int max) {
- setProgress(getString(resourceId), progress, max);
- }
-
- @Override
- public synchronized void setProgress(int progress, int max) {
- setProgress(null, progress, max);
- }
-
- @Override
- public synchronized void setPreventCancel() {
- sendMessageToHandler(ServiceProgressHandler.MessageStatus.PREVENT_CANCEL);
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ConsolidateInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ConsolidateInputParcel.java
new file mode 100644
index 000000000..15d109814
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ConsolidateInputParcel.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 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 ConsolidateInputParcel implements Parcelable {
+
+ public boolean mConsolidateRecovery;
+
+ public ConsolidateInputParcel(boolean consolidateRecovery) {
+ mConsolidateRecovery = consolidateRecovery;
+ }
+
+ protected ConsolidateInputParcel(Parcel in) {
+ mConsolidateRecovery = in.readByte() != 0x00;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByte((byte) (mConsolidateRecovery ? 0x01 : 0x00));
+ }
+
+ public static final Parcelable.Creator<ConsolidateInputParcel> CREATOR = new Parcelable.Creator<ConsolidateInputParcel>() {
+ @Override
+ public ConsolidateInputParcel createFromParcel(Parcel in) {
+ return new ConsolidateInputParcel(in);
+ }
+
+ @Override
+ public ConsolidateInputParcel[] newArray(int size) {
+ return new ConsolidateInputParcel[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java
index 7688b9252..b36d23775 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java
@@ -45,7 +45,7 @@ public class ContactSyncAdapterService extends Service {
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
final SyncResult syncResult) {
- Log.d(Constants.TAG, "Performing a sync!");
+ Log.d(Constants.TAG, "Performing a contact sync!");
// TODO: Import is currently disabled for 2.8, until we implement proper origin management
// importDone.set(false);
// KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DeleteKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DeleteKeyringParcel.java
new file mode 100644
index 000000000..b412a6e2b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DeleteKeyringParcel.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 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 DeleteKeyringParcel implements Parcelable {
+
+ public long[] mMasterKeyIds;
+ public boolean mIsSecret;
+
+ public DeleteKeyringParcel(long[] masterKeyIds, boolean isSecret) {
+ mMasterKeyIds = masterKeyIds;
+ mIsSecret = isSecret;
+ }
+
+ protected DeleteKeyringParcel(Parcel in) {
+ mIsSecret = in.readByte() != 0x00;
+ mMasterKeyIds = in.createLongArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByte((byte) (mIsSecret ? 0x01 : 0x00));
+ dest.writeLongArray(mMasterKeyIds);
+ }
+
+ public static final Parcelable.Creator<DeleteKeyringParcel> CREATOR = new Parcelable.Creator<DeleteKeyringParcel>() {
+ @Override
+ public DeleteKeyringParcel createFromParcel(Parcel in) {
+ return new DeleteKeyringParcel(in);
+ }
+
+ @Override
+ public DeleteKeyringParcel[] newArray(int size) {
+ return new DeleteKeyringParcel[size];
+ }
+ };
+}
+
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
new file mode 100644
index 000000000..24c002bbd
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 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.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[];
+ public String mOutputFile;
+ public Uri mOutputUri;
+ public ExportType mExportType;
+
+ public enum ExportType {
+ UPLOAD_KEYSERVER,
+ EXPORT_FILE,
+ EXPORT_URI
+ }
+
+ public ExportKeyringParcel(String keyserver, Uri keyringUri) {
+ mExportType = ExportType.UPLOAD_KEYSERVER;
+ mKeyserver = keyserver;
+ 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;
+ mExportSecret = exportSecret;
+ mOutputFile = outputFile;
+ }
+
+ @SuppressWarnings("unused") // TODO: is it used?
+ public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, Uri outputUri) {
+ mExportType = ExportType.EXPORT_URI;
+ mMasterKeyIds = masterKeyIds;
+ mExportSecret = exportSecret;
+ mOutputUri = outputUri;
+ }
+
+ 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());
+ mExportType = (ExportType) in.readValue(ExportType.class.getClassLoader());
+ mMasterKeyIds = in.createLongArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ 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);
+ dest.writeValue(mExportType);
+ dest.writeLongArray(mMasterKeyIds);
+ }
+
+ public static final Parcelable.Creator<ExportKeyringParcel> CREATOR = new Parcelable.Creator<ExportKeyringParcel>() {
+ @Override
+ public ExportKeyringParcel createFromParcel(Parcel in) {
+ return new ExportKeyringParcel(in);
+ }
+
+ @Override
+ public ExportKeyringParcel[] newArray(int size) {
+ return new ExportKeyringParcel[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java
new file mode 100644
index 000000000..a41dd71cb
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java
@@ -0,0 +1,74 @@
+/*
+ * 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.service;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
+
+import java.util.ArrayList;
+
+public class ImportKeyringParcel implements Parcelable {
+ // if null, keys are expected to be read from a cache file in ImportExportOperations
+ public ArrayList<ParcelableKeyRing> mKeyList;
+ public String mKeyserver; // must be set if keys are to be imported from a keyserver
+
+ public ImportKeyringParcel (ArrayList<ParcelableKeyRing> keyList, String keyserver) {
+ mKeyList = keyList;
+ mKeyserver = keyserver;
+ }
+
+ protected ImportKeyringParcel(Parcel in) {
+ if (in.readByte() == 0x01) {
+ mKeyList = new ArrayList<>();
+ in.readList(mKeyList, ParcelableKeyRing.class.getClassLoader());
+ } else {
+ mKeyList = null;
+ }
+ mKeyserver = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mKeyList == null) {
+ dest.writeByte((byte) (0x00));
+ } else {
+ dest.writeByte((byte) (0x01));
+ dest.writeList(mKeyList);
+ }
+ dest.writeString(mKeyserver);
+ }
+
+ public static final Parcelable.Creator<ImportKeyringParcel> CREATOR = new Parcelable.Creator<ImportKeyringParcel>() {
+ @Override
+ public ImportKeyringParcel createFromParcel(Parcel in) {
+ return new ImportKeyringParcel(in);
+ }
+
+ @Override
+ public ImportKeyringParcel[] newArray(int size) {
+ return new ImportKeyringParcel[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeybaseVerificationParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeybaseVerificationParcel.java
new file mode 100644
index 000000000..1872191af
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeybaseVerificationParcel.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 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 KeybaseVerificationParcel implements Parcelable {
+
+ public String mKeybaseProof;
+ public String mRequiredFingerprint;
+
+ public KeybaseVerificationParcel(String keybaseProof, String requiredFingerprint) {
+ mKeybaseProof = keybaseProof;
+ mRequiredFingerprint = requiredFingerprint;
+ }
+
+ protected KeybaseVerificationParcel(Parcel in) {
+ mKeybaseProof = in.readString();
+ mRequiredFingerprint = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mKeybaseProof);
+ dest.writeString(mRequiredFingerprint);
+ }
+
+ public static final Parcelable.Creator<KeybaseVerificationParcel> CREATOR = new Parcelable.Creator<KeybaseVerificationParcel>() {
+ @Override
+ public KeybaseVerificationParcel createFromParcel(Parcel in) {
+ return new KeybaseVerificationParcel(in);
+ }
+
+ @Override
+ public KeybaseVerificationParcel[] newArray(int size) {
+ return new KeybaseVerificationParcel[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
deleted file mode 100644
index 63ea6285c..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
+++ /dev/null
@@ -1,752 +0,0 @@
-/*
- * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
- * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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.app.IntentService;
-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 com.textuality.keybase.lib.Proof;
-import com.textuality.keybase.lib.prover.Prover;
-
-import org.json.JSONObject;
-import org.spongycastle.openpgp.PGPUtil;
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
-import org.sufficientlysecure.keychain.keyimport.Keyserver;
-import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
-import org.sufficientlysecure.keychain.operations.CertifyOperation;
-import org.sufficientlysecure.keychain.operations.DeleteOperation;
-import org.sufficientlysecure.keychain.operations.EditKeyOperation;
-import org.sufficientlysecure.keychain.operations.ImportExportOperation;
-import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
-import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
-import org.sufficientlysecure.keychain.operations.results.CertifyResult;
-import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
-import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
-import org.sufficientlysecure.keychain.operations.results.DeleteResult;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
-import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
-import org.sufficientlysecure.keychain.operations.results.CertifyResult;
-import org.sufficientlysecure.keychain.util.FileHelper;
-import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
-import org.sufficientlysecure.keychain.util.Preferences;
-import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
-import org.sufficientlysecure.keychain.keyimport.Keyserver;
-import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
-import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
-import org.sufficientlysecure.keychain.operations.results.OperationResult;
-import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
-import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
-import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
-import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
-import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
-import org.sufficientlysecure.keychain.pgp.Progressable;
-import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
-import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
-import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
-import org.sufficientlysecure.keychain.util.FileHelper;
-import org.sufficientlysecure.keychain.util.InputData;
-import org.sufficientlysecure.keychain.util.Log;
-import org.sufficientlysecure.keychain.util.ParcelableFileCache;
-import org.sufficientlysecure.keychain.util.Passphrase;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import de.measite.minidns.Client;
-import de.measite.minidns.DNSMessage;
-import de.measite.minidns.Question;
-import de.measite.minidns.Record;
-import de.measite.minidns.record.Data;
-import de.measite.minidns.record.TXT;
-
-/**
- * This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
- * data from the activities or other apps, queues these intents, executes them, and stops itself
- * after doing them.
- */
-public class KeychainIntentService extends IntentService implements Progressable {
-
- /* extras that can be given by intent */
- public static final String EXTRA_MESSENGER = "messenger";
- public static final String EXTRA_DATA = "data";
-
- /* possible actions */
- public static final String ACTION_SIGN_ENCRYPT = Constants.INTENT_PREFIX + "SIGN_ENCRYPT";
-
- public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY";
-
- public static final String ACTION_VERIFY_KEYBASE_PROOF = Constants.INTENT_PREFIX + "VERIFY_KEYBASE_PROOF";
-
- public static final String ACTION_DECRYPT_METADATA = Constants.INTENT_PREFIX + "DECRYPT_METADATA";
-
- public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING";
-
- public static final String ACTION_PROMOTE_KEYRING = Constants.INTENT_PREFIX + "PROMOTE_KEYRING";
-
- public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING";
- public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING";
-
- public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
-
- public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
-
- public static final String ACTION_DELETE = Constants.INTENT_PREFIX + "DELETE";
-
- public static final String ACTION_CONSOLIDATE = Constants.INTENT_PREFIX + "CONSOLIDATE";
-
- public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL";
-
- /* keys for data bundle */
-
- // encrypt, decrypt, import export
- public static final String TARGET = "target";
- public static final String SOURCE = "source";
-
- // possible targets:
- public static enum IOType {
- UNKNOWN,
- BYTES,
- URI;
-
- private static final IOType[] values = values();
-
- public static IOType fromInt(int n) {
- if (n < 0 || n >= values.length) {
- return UNKNOWN;
- } else {
- return values[n];
- }
- }
- }
-
- // encrypt
- public static final String ENCRYPT_DECRYPT_INPUT_URI = "input_uri";
- public static final String ENCRYPT_DECRYPT_OUTPUT_URI = "output_uri";
- public static final String SIGN_ENCRYPT_PARCEL = "sign_encrypt_parcel";
-
- // decrypt/verify
- public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
-
- // keybase proof
- public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
- public static final String KEYBASE_PROOF = "keybase_proof";
-
- // save keyring
- public static final String EDIT_KEYRING_PARCEL = "save_parcel";
- public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
- public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
-
- // delete keyring(s)
- public static final String DELETE_KEY_LIST = "delete_list";
- public static final String DELETE_IS_SECRET = "delete_is_secret";
-
- // import key
- public static final String IMPORT_KEY_LIST = "import_key_list";
- public static final String IMPORT_KEY_SERVER = "import_key_server";
-
- // export key
- public static final String EXPORT_FILENAME = "export_filename";
- public static final String EXPORT_URI = "export_uri";
- public static final String EXPORT_SECRET = "export_secret";
- public static final String EXPORT_ALL = "export_all";
- public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
-
- // upload key
- public static final String UPLOAD_KEY_SERVER = "upload_key_server";
-
- // certify key
- public static final String CERTIFY_PARCEL = "certify_parcel";
-
- // promote key
- public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id";
- public static final String PROMOTE_CARD_AID = "promote_card_aid";
-
- // consolidate
- public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
-
-
- /*
- * possible data keys as result send over messenger
- */
-
- // decrypt/verify
- public static final String RESULT_DECRYPTED_BYTES = "decrypted_data";
-
- Messenger mMessenger;
-
- // this attribute can possibly merged with the one above? not sure...
- private AtomicBoolean mActionCanceled = new AtomicBoolean(false);
-
- public KeychainIntentService() {
- super("KeychainIntentService");
- }
-
- /**
- * The IntentService calls this method from the default worker thread with the intent that
- * started the service. When this method returns, IntentService stops the service, as
- * appropriate.
- */
- @Override
- protected void onHandleIntent(Intent intent) {
-
- // We have not been cancelled! (yet)
- mActionCanceled.set(false);
-
- Bundle extras = intent.getExtras();
- if (extras == null) {
- Log.e(Constants.TAG, "Extras bundle is null!");
- return;
- }
-
- if (!(extras.containsKey(EXTRA_MESSENGER) || extras.containsKey(EXTRA_DATA) || (intent
- .getAction() == null))) {
- Log.e(Constants.TAG,
- "Extra bundle must contain a messenger, a data bundle, and an action!");
- return;
- }
-
- Uri dataUri = intent.getData();
-
- mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
- Bundle data = extras.getBundle(EXTRA_DATA);
- if (data == null) {
- Log.e(Constants.TAG, "data extra is null!");
- return;
- }
-
- Log.logDebugBundle(data, "EXTRA_DATA");
-
- ProviderHelper providerHelper = new ProviderHelper(this);
-
- String action = intent.getAction();
-
- // executeServiceMethod action from extra bundle
- switch (action) {
- case ACTION_CERTIFY_KEYRING: {
-
- // Input
- CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL);
- CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
- String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
-
- // Operation
- CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled);
- CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri);
-
- // Result
- sendMessageToHandler(MessageStatus.OKAY, result);
-
- break;
- }
- case ACTION_CONSOLIDATE: {
-
- // Operation
- ConsolidateResult result;
- if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
- result = new ProviderHelper(this).consolidateDatabaseStep2(this);
- } else {
- result = new ProviderHelper(this).consolidateDatabaseStep1(this);
- }
-
- // Result
- sendMessageToHandler(MessageStatus.OKAY, result);
-
- break;
- }
- case ACTION_DECRYPT_METADATA: {
-
- try {
- /* Input */
- CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
-
- InputData inputData = createDecryptInputData(data);
-
- // verifyText and decrypt returning additional resultData values for the
- // verification of signatures
- PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
- this, new ProviderHelper(this), this, inputData, null
- );
- builder.setAllowSymmetricDecryption(true)
- .setDecryptMetadataOnly(true);
-
- DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
-
- sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult);
- } catch (Exception e) {
- sendErrorToHandler(e);
- }
-
- break;
- }
- case ACTION_VERIFY_KEYBASE_PROOF: {
-
- try {
- Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF)));
- setProgress(R.string.keybase_message_fetching_data, 0, 100);
-
- Prover prover = Prover.findProverFor(proof);
-
- if (prover == null) {
- sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof.getPrettyName());
- return;
- }
-
- if (!prover.fetchProofData()) {
- sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence));
- return;
- }
- String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT);
- if (!prover.checkFingerprint(requiredFingerprint)) {
- sendProofError(getString(R.string.keybase_key_mismatch));
- return;
- }
-
- String domain = prover.dnsTxtCheckRequired();
- if (domain != null) {
- DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT));
- if (dnsQuery == null) {
- sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure));
- return;
- }
- Record[] records = dnsQuery.getAnswers();
- List<List<byte[]>> extents = new ArrayList<List<byte[]>>();
- for (Record r : records) {
- Data d = r.getPayload();
- if (d instanceof TXT) {
- extents.add(((TXT) d).getExtents());
- }
- }
- if (!prover.checkDnsTxt(extents)) {
- sendProofError(prover.getLog(), null);
- return;
- }
- }
-
- byte[] messageBytes = prover.getPgpMessage().getBytes();
- if (prover.rawMessageCheckRequired()) {
- InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(messageBytes));
- if (!prover.checkRawMessageBytes(messageByteStream)) {
- sendProofError(prover.getLog(), null);
- return;
- }
- }
-
- // kind of awkward, but this whole class wants to pull bytes out of “data”
- data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());
- data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, messageBytes);
-
- InputData inputData = createDecryptInputData(data);
- OutputStream outStream = createCryptOutputStream(data);
-
- PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
- this, new ProviderHelper(this), this,
- inputData, outStream
- );
- builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint);
-
- DecryptVerifyResult decryptVerifyResult = builder.build().execute(
- new CryptoInputParcel());
- outStream.close();
-
- if (!decryptVerifyResult.success()) {
- OperationLog log = decryptVerifyResult.getLog();
- OperationResult.LogEntryParcel lastEntry = null;
- for (OperationResult.LogEntryParcel entry : log) {
- lastEntry = entry;
- }
- sendProofError(getString(lastEntry.mType.getMsgId()));
- return;
- }
-
- if (!prover.validate(outStream.toString())) {
- sendProofError(getString(R.string.keybase_message_payload_mismatch));
- return;
- }
-
- Bundle resultData = new Bundle();
- resultData.putString(ServiceProgressHandler.DATA_MESSAGE, "OK");
-
- // these help the handler construct a useful human-readable message
- resultData.putString(ServiceProgressHandler.KEYBASE_PROOF_URL, prover.getProofUrl());
- resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl());
- resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_LABEL, prover.getPresenceLabel());
- sendMessageToHandler(MessageStatus.OKAY, resultData);
- } catch (Exception e) {
- sendErrorToHandler(e);
- }
-
- break;
- }
- case ACTION_DECRYPT_VERIFY: {
-
- try {
- /* Input */
- CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
-
- InputData inputData = createDecryptInputData(data);
- OutputStream outStream = createCryptOutputStream(data);
-
- /* Operation */
- Bundle resultData = new Bundle();
-
- // verifyText and decrypt returning additional resultData values for the
- // verification of signatures
- PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
- this, new ProviderHelper(this), this,
- inputData, outStream
- );
- builder.setAllowSymmetricDecryption(true);
-
- DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
-
- outStream.close();
-
- resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult);
-
- /* Output */
- finalizeDecryptOutputStream(data, resultData, outStream);
- Log.logDebugBundle(resultData, "resultData");
-
- sendMessageToHandler(MessageStatus.OKAY, resultData);
-
- } catch (IOException | PgpGeneralException e) {
- // TODO get rid of this!
- sendErrorToHandler(e);
- }
-
- break;
- }
- case ACTION_DELETE: {
-
- // Input
- long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST);
- boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
-
- // Operation
- DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this);
- DeleteResult result = op.execute(masterKeyIds, isSecret);
-
- // Result
- sendMessageToHandler(MessageStatus.OKAY, result);
-
- break;
- }
- case ACTION_EDIT_KEYRING: {
-
- // Input
- SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
- CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
-
- // Operation
- EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
- OperationResult result = op.execute(saveParcel, cryptoInput);
-
- // Result
- sendMessageToHandler(MessageStatus.OKAY, result);
-
- break;
- }
- case ACTION_PROMOTE_KEYRING: {
-
- // Input
- long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID);
- byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID);
-
- // Operation
- PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled);
- PromoteKeyResult result = op.execute(keyRingId, cardAid);
-
- // Result
- sendMessageToHandler(MessageStatus.OKAY, result);
-
- break;
- }
- case ACTION_EXPORT_KEYRING: {
-
- // Input
- boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
- String outputFile = data.getString(EXPORT_FILENAME);
- Uri outputUri = data.getParcelable(EXPORT_URI);
-
- boolean exportAll = data.getBoolean(EXPORT_ALL);
- long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
-
- // Operation
- ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
- ExportResult result;
- if (outputFile != null) {
- result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile);
- } else {
- result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri);
- }
-
- // Result
- sendMessageToHandler(MessageStatus.OKAY, result);
-
- break;
- }
- case ACTION_IMPORT_KEYRING: {
-
- // Input
- String keyServer = data.getString(IMPORT_KEY_SERVER);
- ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST);
- ParcelableFileCache<ParcelableKeyRing> cache =
- new ParcelableFileCache<>(this, "key_import.pcl");
-
- // Operation
- ImportExportOperation importExportOperation = new ImportExportOperation(
- this, providerHelper, this, mActionCanceled);
- // Either list or cache must be null, no guarantees otherwise.
- ImportKeyResult result = list != null
- ? importExportOperation.importKeyRings(list, keyServer)
- : importExportOperation.importKeyRings(cache, keyServer);
-
- // Result
- sendMessageToHandler(MessageStatus.OKAY, result);
-
- break;
-
- }
- case ACTION_SIGN_ENCRYPT: {
-
- // Input
- SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL);
- CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
-
- // Operation
- SignEncryptOperation op = new SignEncryptOperation(
- this, new ProviderHelper(this), this, mActionCanceled);
- SignEncryptResult result = op.execute(inputParcel, cryptoInput);
-
- // Result
- sendMessageToHandler(MessageStatus.OKAY, result);
-
- break;
- }
- case ACTION_UPLOAD_KEYRING: {
- try {
-
- /* Input */
- String keyServer = data.getString(UPLOAD_KEY_SERVER);
- // and dataUri!
-
- /* Operation */
- HkpKeyserver server = new HkpKeyserver(keyServer);
-
- CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
- ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
-
- try {
- importExportOperation.uploadKeyRingToServer(server, keyring);
- } catch (Keyserver.AddKeyException e) {
- throw new PgpGeneralException("Unable to export key to selected server");
- }
-
- sendMessageToHandler(MessageStatus.OKAY);
- } catch (Exception e) {
- sendErrorToHandler(e);
- }
- break;
- }
- }
- }
-
- private void sendProofError(List<String> log, String label) {
- String msg = null;
- label = (label == null) ? "" : label + ": ";
- for (String m : log) {
- Log.e(Constants.TAG, label + m);
- msg = m;
- }
- sendProofError(label + msg);
- }
-
- private void sendProofError(String msg) {
- Bundle bundle = new Bundle();
- bundle.putString(ServiceProgressHandler.DATA_ERROR, msg);
- sendMessageToHandler(MessageStatus.OKAY, bundle);
- }
-
- private void sendErrorToHandler(Exception e) {
- // TODO: Implement a better exception handling here
- // contextualize the exception, if necessary
- String message;
- if (e instanceof PgpGeneralMsgIdException) {
- e = ((PgpGeneralMsgIdException) e).getContextualized(this);
- message = e.getMessage();
- } else {
- message = e.getMessage();
- }
- Log.d(Constants.TAG, "KeychainIntentService Exception: ", e);
-
- Bundle data = new Bundle();
- data.putString(ServiceProgressHandler.DATA_ERROR, message);
- sendMessageToHandler(MessageStatus.EXCEPTION, null, data);
- }
-
- private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) {
-
- Message msg = Message.obtain();
- assert msg != null;
- msg.arg1 = status.ordinal();
- if (arg2 != null) {
- msg.arg2 = arg2;
- }
- if (data != null) {
- msg.setData(data);
- }
-
- try {
- mMessenger.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);
- }
- }
-
- private void sendMessageToHandler(MessageStatus status, OperationResult data) {
- Bundle bundle = new Bundle();
- bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
- sendMessageToHandler(status, null, bundle);
- }
-
- private void sendMessageToHandler(MessageStatus status, Bundle data) {
- sendMessageToHandler(status, null, data);
- }
-
- private void sendMessageToHandler(MessageStatus status) {
- sendMessageToHandler(status, null, null);
- }
-
- /**
- * Set progress of ProgressDialog by sending message to handler on UI thread
- */
- public void setProgress(String message, int progress, int max) {
- Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
- + max);
-
- Bundle data = new Bundle();
- if (message != null) {
- data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
- }
- data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
- data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
-
- sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data);
- }
-
- public void setProgress(int resourceId, int progress, int max) {
- setProgress(getString(resourceId), progress, max);
- }
-
- public void setProgress(int progress, int max) {
- setProgress(null, progress, max);
- }
-
- @Override
- public void setPreventCancel() {
- sendMessageToHandler(MessageStatus.PREVENT_CANCEL);
- }
-
- private InputData createDecryptInputData(Bundle data) throws IOException, PgpGeneralException {
- return createCryptInputData(data, DECRYPT_CIPHERTEXT_BYTES);
- }
-
- private InputData createCryptInputData(Bundle data, String bytesName) throws PgpGeneralException, IOException {
- int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET);
- IOType type = IOType.fromInt(source);
- switch (type) {
- case BYTES: /* encrypting bytes directly */
- byte[] bytes = data.getByteArray(bytesName);
- return new InputData(new ByteArrayInputStream(bytes), bytes.length);
-
- case URI: /* encrypting content uri */
- Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_INPUT_URI);
-
- // InputStream
- return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0));
-
- default:
- throw new PgpGeneralException("No target chosen!");
- }
- }
-
- private OutputStream createCryptOutputStream(Bundle data) throws PgpGeneralException, FileNotFoundException {
- int target = data.getInt(TARGET);
- IOType type = IOType.fromInt(target);
- switch (type) {
- case BYTES:
- return new ByteArrayOutputStream();
-
- case URI:
- Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_OUTPUT_URI);
-
- return getContentResolver().openOutputStream(providerUri);
-
- default:
- throw new PgpGeneralException("No target chosen!");
- }
- }
-
- private void finalizeDecryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) {
- finalizeCryptOutputStream(data, resultData, outStream, RESULT_DECRYPTED_BYTES);
- }
-
- private void finalizeCryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream, String bytesName) {
- int target = data.getInt(TARGET);
- IOType type = IOType.fromInt(target);
- switch (type) {
- case BYTES:
- byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
- resultData.putByteArray(bytesName, output);
- break;
- case URI:
- // nothing, output was written, just send okay and verification bundle
-
- break;
- }
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (ACTION_CANCEL.equals(intent.getAction())) {
- mActionCanceled.set(true);
- return START_NOT_STICKY;
- }
- return super.onStartCommand(intent, flags, startId);
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
new file mode 100644
index 000000000..eff27f112
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.operations.BaseOperation;
+import org.sufficientlysecure.keychain.operations.CertifyOperation;
+import org.sufficientlysecure.keychain.operations.ConsolidateOperation;
+import org.sufficientlysecure.keychain.operations.DeleteOperation;
+import org.sufficientlysecure.keychain.operations.EditKeyOperation;
+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.PgpDecryptVerifyOperation;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
+ * data from the activities or other apps, executes them, and stops itself after doing them.
+ */
+public class KeychainService extends Service implements Progressable {
+
+ // messenger for communication (hack)
+ public static final String EXTRA_MESSENGER = "messenger";
+
+ // extras for operation
+ public static final String EXTRA_OPERATION_INPUT = "op_input";
+ public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
+
+ public static final String ACTION_CANCEL = "action_cancel";
+
+ // this attribute can possibly merged with the one above? not sure...
+ private AtomicBoolean mActionCanceled = new AtomicBoolean(false);
+
+ ThreadLocal<Messenger> mMessenger = new ThreadLocal<>();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ /**
+ * This is run on the main thread, we need to spawn a runnable which runs on another thread for the actual operation
+ */
+ @Override
+ public int onStartCommand(final Intent intent, int flags, int startId) {
+
+ if (intent.getAction() != null && intent.getAction().equals(ACTION_CANCEL)) {
+ mActionCanceled.set(true);
+ return START_NOT_STICKY;
+ }
+
+ Runnable actionRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // We have not been cancelled! (yet)
+ mActionCanceled.set(false);
+
+ Bundle extras = intent.getExtras();
+
+ // Set messenger for communication (for this particular thread)
+ mMessenger.set(extras.<Messenger>getParcelable(EXTRA_MESSENGER));
+
+ // Input
+ Parcelable inputParcel = extras.getParcelable(EXTRA_OPERATION_INPUT);
+ CryptoInputParcel cryptoInput = extras.getParcelable(EXTRA_CRYPTO_INPUT);
+
+ // Operation
+ BaseOperation op;
+
+ // just for brevity
+ KeychainService outerThis = KeychainService.this;
+ if (inputParcel instanceof SignEncryptParcel) {
+ op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis),
+ outerThis, mActionCanceled);
+ } else if (inputParcel instanceof PgpDecryptVerifyInputParcel) {
+ op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis);
+ } 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);
+ } else if (inputParcel instanceof DeleteKeyringParcel) {
+ op = new DeleteOperation(outerThis, new ProviderHelper(outerThis), outerThis);
+ } else if (inputParcel instanceof PromoteKeyringParcel) {
+ op = new PromoteKeyOperation(outerThis, new ProviderHelper(outerThis),
+ outerThis, mActionCanceled);
+ } else if (inputParcel instanceof ImportKeyringParcel) {
+ op = new ImportOperation(outerThis, new ProviderHelper(outerThis), outerThis,
+ mActionCanceled);
+ } else if (inputParcel instanceof ExportKeyringParcel) {
+ op = new ExportOperation(outerThis, new ProviderHelper(outerThis), outerThis,
+ mActionCanceled);
+ } else if (inputParcel instanceof ConsolidateInputParcel) {
+ op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis),
+ outerThis);
+ } else if (inputParcel instanceof KeybaseVerificationParcel) {
+ op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis),
+ outerThis);
+ } else {
+ throw new AssertionError("Unrecognized input parcel in KeychainService!");
+ }
+
+ @SuppressWarnings("unchecked") // this is unchecked, we make sure it's the correct op above!
+ OperationResult result = op.execute(inputParcel, cryptoInput);
+ sendMessageToHandler(MessageStatus.OKAY, result);
+
+ }
+ };
+
+ Thread actionThread = new Thread(actionRunnable);
+ actionThread.start();
+
+ return START_NOT_STICKY;
+ }
+
+ private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) {
+
+ Message msg = Message.obtain();
+ assert msg != null;
+ msg.arg1 = status.ordinal();
+ if (arg2 != null) {
+ msg.arg2 = arg2;
+ }
+ if (data != null) {
+ msg.setData(data);
+ }
+
+ try {
+ mMessenger.get().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);
+ }
+ }
+
+ private void sendMessageToHandler(MessageStatus status, OperationResult data) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
+ sendMessageToHandler(status, null, bundle);
+ }
+
+ private void sendMessageToHandler(MessageStatus status) {
+ sendMessageToHandler(status, null, null);
+ }
+
+ /**
+ * Set progress of ProgressDialog by sending message to handler on UI thread
+ */
+ @Override
+ public void setProgress(String message, int progress, int max) {
+ Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
+ + max);
+
+ Bundle data = new Bundle();
+ if (message != null) {
+ data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
+ }
+ data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
+ data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
+
+ sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data);
+ }
+
+ @Override
+ public void setProgress(int resourceId, int progress, int max) {
+ setProgress(getString(resourceId), progress, max);
+ }
+
+ @Override
+ public void setProgress(int progress, int max) {
+ setProgress(null, progress, max);
+ }
+
+ @Override
+ public void setPreventCancel() {
+ sendMessageToHandler(MessageStatus.PREVENT_CANCEL);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java
new file mode 100644
index 000000000..3243df1a8
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java
@@ -0,0 +1,516 @@
+package org.sufficientlysecure.keychain.service;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SyncResult;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.support.v4.app.NotificationCompat;
+import android.widget.Toast;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
+import org.sufficientlysecure.keychain.operations.ImportOperation;
+import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity;
+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.GregorianCalendar;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class KeyserverSyncAdapterService extends Service {
+
+ // how often a sync should be initiated, in s
+ public static final long SYNC_INTERVAL =
+ Constants.DEBUG_KEYSERVER_SYNC
+ ? TimeUnit.MINUTES.toSeconds(2) : TimeUnit.DAYS.toSeconds(3);
+ // time since last update after which a key should be updated again, in s
+ public static final long KEY_UPDATE_LIMIT =
+ Constants.DEBUG_KEYSERVER_SYNC ? 1 : TimeUnit.DAYS.toSeconds(7);
+ // time by which a sync is postponed in case of a
+ public static final long SYNC_POSTPONE_TIME =
+ Constants.DEBUG_KEYSERVER_SYNC ? 30 * 1000 : TimeUnit.MINUTES.toMillis(5);
+ // Time taken by Orbot before a new circuit is created
+ public static final int ORBOT_CIRCUIT_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(10);
+
+
+ private static final String ACTION_IGNORE_TOR = "ignore_tor";
+ private static final String ACTION_UPDATE_ALL = "update_all";
+ private static final String ACTION_SYNC_NOW = "sync_now";
+ private static final String ACTION_DISMISS_NOTIFICATION = "cancel_sync";
+ private static final String ACTION_START_ORBOT = "start_orbot";
+ private static final String ACTION_CANCEL = "cancel";
+
+ private AtomicBoolean mCancelled = new AtomicBoolean(false);
+
+ @Override
+ public int onStartCommand(final Intent intent, int flags, final int startId) {
+ switch (intent.getAction()) {
+ case ACTION_CANCEL: {
+ mCancelled.set(true);
+ break;
+ }
+ // the reason for the separation betweyeen SYNC_NOW and UPDATE_ALL is so that starting
+ // the sync directly from the notification is possible while the screen is on with
+ // UPDATE_ALL, but a postponed sync is only started if screen is off
+ case ACTION_SYNC_NOW: {
+ // this checks for screen on/off before sync, and postpones the sync if on
+ ContentResolver.requestSync(
+ new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE),
+ Constants.PROVIDER_AUTHORITY,
+ new Bundle()
+ );
+ break;
+ }
+ case ACTION_UPDATE_ALL: {
+ // does not check for screen on/off
+ asyncKeyUpdate(this, new CryptoInputParcel());
+ break;
+ }
+ case ACTION_IGNORE_TOR: {
+ NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
+ asyncKeyUpdate(this, new CryptoInputParcel(ParcelableProxy.getForNoProxy()));
+ break;
+ }
+ case ACTION_START_ORBOT: {
+ NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
+ Intent startOrbot = new Intent(this, OrbotRequiredDialogActivity.class);
+ startOrbot.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_START_ORBOT, true);
+ Messenger messenger = new Messenger(
+ new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case OrbotRequiredDialogActivity.MESSAGE_ORBOT_STARTED: {
+ asyncKeyUpdate(KeyserverSyncAdapterService.this,
+ new CryptoInputParcel());
+ break;
+ }
+ case OrbotRequiredDialogActivity.MESSAGE_ORBOT_IGNORE: {
+ asyncKeyUpdate(KeyserverSyncAdapterService.this,
+ new CryptoInputParcel(
+ ParcelableProxy.getForNoProxy()));
+ break;
+ }
+ case OrbotRequiredDialogActivity.MESSAGE_DIALOG_CANCEL: {
+ // just stop service
+ stopSelf();
+ break;
+ }
+ }
+ }
+ }
+ );
+ startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_MESSENGER, messenger);
+ startActivity(startOrbot);
+ break;
+ }
+ case ACTION_DISMISS_NOTIFICATION: {
+ NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
+ stopSelf(startId);
+ break;
+ }
+ }
+ return START_NOT_STICKY;
+ }
+
+ private class KeyserverSyncAdapter extends AbstractThreadedSyncAdapter {
+
+ public KeyserverSyncAdapter() {
+ super(KeyserverSyncAdapterService.this, true);
+ }
+
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority,
+ ContentProviderClient provider, SyncResult syncResult) {
+ Log.d(Constants.TAG, "Performing a keyserver sync!");
+
+ PowerManager pm = (PowerManager) KeyserverSyncAdapterService.this
+ .getSystemService(Context.POWER_SERVICE);
+ @SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20
+ boolean isScreenOn = pm.isScreenOn();
+
+ if (!isScreenOn) {
+ Intent serviceIntent = new Intent(KeyserverSyncAdapterService.this,
+ KeyserverSyncAdapterService.class);
+ serviceIntent.setAction(ACTION_UPDATE_ALL);
+ startService(serviceIntent);
+ } else {
+ postponeSync();
+ }
+ }
+
+ @Override
+ public void onSyncCanceled() {
+ super.onSyncCanceled();
+ cancelUpdates(KeyserverSyncAdapterService.this);
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new KeyserverSyncAdapter().getSyncAdapterBinder();
+ }
+
+ private void handleUpdateResult(ImportKeyResult result) {
+ if (result.isPending()) {
+ // result is pending due to Orbot not being started
+ // try to start it silently, if disabled show notifications
+ new OrbotHelper.SilentStartManager() {
+ @Override
+ protected void onOrbotStarted() {
+ // retry the update
+ asyncKeyUpdate(KeyserverSyncAdapterService.this,
+ new CryptoInputParcel());
+ }
+
+ @Override
+ protected void onSilentStartDisabled() {
+ // show notification
+ NotificationManager manager =
+ (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT,
+ getOrbotNoification(KeyserverSyncAdapterService.this));
+ }
+ }.startOrbotAndListen(this, false);
+ } else if (isUpdateCancelled()) {
+ Log.d(Constants.TAG, "Keyserver sync cancelled, postponing by" + SYNC_POSTPONE_TIME
+ + "ms");
+ postponeSync();
+ } else {
+ Log.d(Constants.TAG, "Keyserver sync completed: Updated: " + result.mUpdatedKeys
+ + " Failed: " + result.mBadKeys);
+ stopSelf();
+ }
+ }
+
+ private void postponeSync() {
+ AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ Intent serviceIntent = new Intent(this, KeyserverSyncAdapterService.class);
+ serviceIntent.setAction(ACTION_SYNC_NOW);
+ PendingIntent pi = PendingIntent.getService(this, 0, serviceIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + SYNC_POSTPONE_TIME,
+ pi
+ );
+ }
+
+ private void asyncKeyUpdate(final Context context,
+ final CryptoInputParcel cryptoInputParcel) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ ImportKeyResult result = updateKeysFromKeyserver(context, cryptoInputParcel);
+ handleUpdateResult(result);
+ }
+ }).start();
+ }
+
+ private synchronized ImportKeyResult updateKeysFromKeyserver(final Context context,
+ final CryptoInputParcel cryptoInputParcel) {
+ mCancelled.set(false);
+
+ ArrayList<ParcelableKeyRing> keyList = getKeysToUpdate(context);
+
+ if (isUpdateCancelled()) { // if we've already been cancelled
+ return new ImportKeyResult(OperationResult.RESULT_CANCELLED,
+ new OperationResult.OperationLog());
+ }
+
+ if (cryptoInputParcel.getParcelableProxy() == null) {
+ // no explicit proxy, retrieve from preferences. Check if we should do a staggered sync
+ if (Preferences.getPreferences(context).getProxyPrefs().torEnabled) {
+ return staggeredUpdate(context, keyList, cryptoInputParcel);
+ } else {
+ return directUpdate(context, keyList, cryptoInputParcel);
+ }
+ } else {
+ return directUpdate(context, keyList, cryptoInputParcel);
+ }
+ }
+
+ private ImportKeyResult directUpdate(Context context, ArrayList<ParcelableKeyRing> keyList,
+ CryptoInputParcel cryptoInputParcel) {
+ Log.d(Constants.TAG, "Starting normal update");
+ ImportOperation importOp = new ImportOperation(context, new ProviderHelper(context), null);
+ return importOp.execute(
+ new ImportKeyringParcel(keyList,
+ Preferences.getPreferences(context).getPreferredKeyserver()),
+ cryptoInputParcel
+ );
+ }
+
+
+ /**
+ * will perform a staggered update of user's keys using delays to ensure new Tor circuits, as
+ * performed by parcimonie. Relevant issue and method at:
+ * https://github.com/open-keychain/open-keychain/issues/1337
+ *
+ * @return result of the sync
+ */
+ private ImportKeyResult staggeredUpdate(Context context, ArrayList<ParcelableKeyRing> keyList,
+ CryptoInputParcel cryptoInputParcel) {
+ Log.d(Constants.TAG, "Starting staggered update");
+ // final int WEEK_IN_SECONDS = (int) TimeUnit.DAYS.toSeconds(7);
+ final int WEEK_IN_SECONDS = 0;
+ ImportOperation.KeyImportAccumulator accumulator
+ = new ImportOperation.KeyImportAccumulator(keyList.size(), null);
+ for (ParcelableKeyRing keyRing : keyList) {
+ int waitTime;
+ int staggeredTime = new Random().nextInt(1 + 2 * (WEEK_IN_SECONDS / keyList.size()));
+ if (staggeredTime >= ORBOT_CIRCUIT_TIMEOUT) {
+ waitTime = staggeredTime;
+ } else {
+ waitTime = ORBOT_CIRCUIT_TIMEOUT + new Random().nextInt(ORBOT_CIRCUIT_TIMEOUT);
+ }
+ Log.d(Constants.TAG, "Updating key with fingerprint " + keyRing.mExpectedFingerprint +
+ " with a wait time of " + waitTime + "s");
+ try {
+ Thread.sleep(waitTime * 1000);
+ } catch (InterruptedException e) {
+ Log.e(Constants.TAG, "Exception during sleep between key updates", e);
+ // skip this one
+ continue;
+ }
+ ArrayList<ParcelableKeyRing> keyWrapper = new ArrayList<>();
+ keyWrapper.add(keyRing);
+ if (isUpdateCancelled()) {
+ return new ImportKeyResult(ImportKeyResult.RESULT_CANCELLED,
+ new OperationResult.OperationLog());
+ }
+ ImportKeyResult result =
+ new ImportOperation(context, new ProviderHelper(context), null, mCancelled)
+ .execute(
+ new ImportKeyringParcel(
+ keyWrapper,
+ Preferences.getPreferences(context)
+ .getPreferredKeyserver()
+ ),
+ cryptoInputParcel
+ );
+ if (result.isPending()) {
+ return result;
+ }
+ accumulator.accumulateKeyImport(result);
+ }
+ return accumulator.getConsolidatedResult();
+ }
+
+ /**
+ * 1. Get keys which have been updated recently and therefore do not need to
+ * be updated now
+ * 2. Get list of all keys and filter out ones that don't need to be updated
+ * 3. Return keys to be updated
+ *
+ * @return list of keys that require update
+ */
+ private ArrayList<ParcelableKeyRing> getKeysToUpdate(Context context) {
+
+ // 1. Get keys which have been updated recently and don't need to updated now
+ final int INDEX_UPDATED_KEYS_MASTER_KEY_ID = 0;
+ final int INDEX_LAST_UPDATED = 1;
+
+ // all time in seconds not milliseconds
+ final long CURRENT_TIME = GregorianCalendar.getInstance().getTimeInMillis() / 1000;
+ Cursor updatedKeysCursor = context.getContentResolver().query(
+ KeychainContract.UpdatedKeys.CONTENT_URI,
+ new String[]{
+ KeychainContract.UpdatedKeys.MASTER_KEY_ID,
+ KeychainContract.UpdatedKeys.LAST_UPDATED
+ },
+ "? - " + KeychainContract.UpdatedKeys.LAST_UPDATED + " < " + KEY_UPDATE_LIMIT,
+ new String[]{"" + CURRENT_TIME},
+ null
+ );
+
+ ArrayList<Long> ignoreMasterKeyIds = new ArrayList<>();
+ while (updatedKeysCursor.moveToNext()) {
+ long masterKeyId = updatedKeysCursor.getLong(INDEX_UPDATED_KEYS_MASTER_KEY_ID);
+ Log.d(Constants.TAG, "Keyserver sync: Ignoring {" + masterKeyId + "} last updated at {"
+ + updatedKeysCursor.getLong(INDEX_LAST_UPDATED) + "}s");
+ ignoreMasterKeyIds.add(masterKeyId);
+ }
+ updatedKeysCursor.close();
+
+ // 2. Make a list of public keys which should be updated
+ final int INDEX_MASTER_KEY_ID = 0;
+ final int INDEX_FINGERPRINT = 1;
+ Cursor keyCursor = context.getContentResolver().query(
+ KeychainContract.KeyRings.buildUnifiedKeyRingsUri(),
+ new String[]{
+ KeychainContract.KeyRings.MASTER_KEY_ID,
+ KeychainContract.KeyRings.FINGERPRINT
+ },
+ null,
+ null,
+ null
+ );
+
+ if (keyCursor == null) {
+ return new ArrayList<>();
+ }
+
+ ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
+ while (keyCursor.moveToNext()) {
+ long keyId = keyCursor.getLong(INDEX_MASTER_KEY_ID);
+ if (ignoreMasterKeyIds.contains(keyId)) {
+ continue;
+ }
+ Log.d(Constants.TAG, "Keyserver sync: Updating {" + keyId + "}");
+ String fingerprint = KeyFormattingUtils
+ .convertFingerprintToHex(keyCursor.getBlob(INDEX_FINGERPRINT));
+ String hexKeyId = KeyFormattingUtils
+ .convertKeyIdToHex(keyId);
+ // we aren't updating from keybase as of now
+ keyList.add(new ParcelableKeyRing(fingerprint, hexKeyId, null));
+ }
+ keyCursor.close();
+
+ return keyList;
+ }
+
+ private boolean isUpdateCancelled() {
+ return mCancelled.get();
+ }
+
+ /**
+ * will cancel an update already in progress. We send an Intent to cancel it instead of simply
+ * modifying a static variable sync the service is running in a process that is different from
+ * the default application process where the UI code runs.
+ *
+ * @param context used to send an Intent to the service requesting cancellation.
+ */
+ public static void cancelUpdates(Context context) {
+ Intent intent = new Intent(context, KeyserverSyncAdapterService.class);
+ intent.setAction(ACTION_CANCEL);
+ context.startService(intent);
+ }
+
+ private Notification getOrbotNoification(Context context) {
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
+ builder.setSmallIcon(R.drawable.ic_stat_notify_24dp)
+ .setLargeIcon(getBitmap(R.drawable.ic_launcher, context))
+ .setContentTitle(context.getString(R.string.keyserver_sync_orbot_notif_title))
+ .setContentText(context.getString(R.string.keyserver_sync_orbot_notif_msg))
+ .setAutoCancel(true);
+
+ // In case the user decides to not use tor
+ Intent ignoreTorIntent = new Intent(context, KeyserverSyncAdapterService.class);
+ ignoreTorIntent.setAction(ACTION_IGNORE_TOR);
+ PendingIntent ignoreTorPi = PendingIntent.getService(
+ context,
+ 0, // security not issue since we're giving this pending intent to Notification Manager
+ ignoreTorIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT
+ );
+
+ builder.addAction(R.drawable.ic_stat_tor_off,
+ context.getString(R.string.keyserver_sync_orbot_notif_ignore),
+ ignoreTorPi);
+
+ Intent startOrbotIntent = new Intent(context, KeyserverSyncAdapterService.class);
+ startOrbotIntent.setAction(ACTION_START_ORBOT);
+ PendingIntent startOrbotPi = PendingIntent.getService(
+ context,
+ 0, // security not issue since we're giving this pending intent to Notification Manager
+ startOrbotIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT
+ );
+
+ builder.addAction(R.drawable.ic_stat_tor,
+ context.getString(R.string.keyserver_sync_orbot_notif_start),
+ startOrbotPi
+ );
+ builder.setContentIntent(startOrbotPi);
+
+ return builder.build();
+ }
+
+ public static void enableKeyserverSync(Context context) {
+ try {
+ AccountManager manager = AccountManager.get(context);
+ Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE);
+
+ Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE);
+ if (accounts.length == 0) {
+ if (!manager.addAccountExplicitly(account, null, null)) {
+ Log.e(Constants.TAG, "Adding account failed!");
+ }
+ }
+ // for keyserver sync
+ ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1);
+ ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY,
+ true);
+ ContentResolver.addPeriodicSync(
+ account,
+ Constants.PROVIDER_AUTHORITY,
+ new Bundle(),
+ SYNC_INTERVAL
+ );
+ } catch (SecurityException e) {
+ Log.e(Constants.TAG, "SecurityException when adding the account", e);
+ Toast.makeText(context, R.string.reinstall_openkeychain, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ // from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android
+ private Bitmap getBitmap(int resId, Context context) {
+ int mLargeIconWidth = (int) context.getResources().getDimension(
+ android.R.dimen.notification_large_icon_width);
+ int mLargeIconHeight = (int) context.getResources().getDimension(
+ android.R.dimen.notification_large_icon_height);
+ Drawable d;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ // noinspection deprecation (can't help it at this api level)
+ d = context.getResources().getDrawable(resId);
+ } else {
+ d = context.getDrawable(resId);
+ }
+ if (d == null) {
+ return null;
+ }
+ Bitmap b = Bitmap.createBitmap(mLargeIconWidth, mLargeIconHeight, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+ d.setBounds(0, 0, mLargeIconWidth, mLargeIconHeight);
+ d.draw(c);
+ return b;
+ }
+}
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 78137170d..be269c66d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -17,6 +17,9 @@
package org.sufficientlysecure.keychain.service;
+
+import java.util.Date;
+
import android.app.AlarmManager;
import android.app.Notification;
import android.app.PendingIntent;
@@ -25,8 +28,13 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Build;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -46,8 +54,6 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
-import java.util.Date;
-
/**
* This service runs in its own process, but is available to all other processes as the main
* passphrase cache. Use the static methods addCachedPassphrase and getCachedPassphrase for
@@ -95,8 +101,6 @@ public class PassphraseCacheService extends Service {
private static final long DEFAULT_TTL = 15;
- private static final int NOTIFICATION_ID = 1;
-
private static final int MSG_PASSPHRASE_CACHE_GET_OKAY = 1;
private static final int MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND = 2;
@@ -149,6 +153,14 @@ public class PassphraseCacheService extends Service {
context.startService(intent);
}
+ public static void clearCachedPassphrases(Context context) {
+ Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase()");
+
+ Intent intent = new Intent(context, PassphraseCacheService.class);
+ intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
+
+ context.startService(intent);
+ }
/**
* Gets a cached passphrase from memory by sending an intent to the service. This method is
@@ -218,21 +230,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
@@ -309,7 +321,7 @@ public class PassphraseCacheService extends Service {
if (action.equals(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE)) {
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
- timeout(context, keyId);
+ timeout(keyId);
}
}
};
@@ -411,9 +423,9 @@ public class PassphraseCacheService extends Service {
long referenceKeyId;
if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
- referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L);
- } else {
referenceKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, 0L);
+ } else {
+ referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L);
}
// Stop specific ttl alarm and
am.cancel(buildIntent(this, referenceKeyId));
@@ -444,12 +456,17 @@ public class PassphraseCacheService extends Service {
/**
* Called when one specific passphrase for keyId timed out
*/
- private void timeout(Context context, long keyId) {
+ private void timeout(long keyId) {
+
CachedPassphrase cPass = mPassphraseCache.get(keyId);
- // clean internal char[] from memory!
- cPass.getPassphrase().removeFromMemory();
- // remove passphrase object
- mPassphraseCache.remove(keyId);
+ if (cPass != null) {
+ if (cPass.getPassphrase() != null) {
+ // clean internal char[] from memory!
+ cPass.getPassphrase().removeFromMemory();
+ }
+ // remove passphrase object
+ mPassphraseCache.remove(keyId);
+ }
Log.d(Constants.TAG, "PassphraseCacheService Timeout of keyId " + keyId + ", removed from memory!");
@@ -458,7 +475,7 @@ public class PassphraseCacheService extends Service {
private void updateService() {
if (mPassphraseCache.size() > 0) {
- startForeground(NOTIFICATION_ID, getNotification());
+ startForeground(Constants.Notification.PASSPHRASE_CACHE, getNotification());
} else {
// stop whole service if no cached passphrases remaining
Log.d(Constants.TAG, "PassphraseCacheService: No passphrases remaining in memory, stopping service!");
@@ -466,59 +483,67 @@ public class PassphraseCacheService extends Service {
}
}
+ // from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android
+ private static Bitmap getBitmap(int resId, Context context) {
+ int mLargeIconWidth = (int) context.getResources().getDimension(
+ android.R.dimen.notification_large_icon_width);
+ int mLargeIconHeight = (int) context.getResources().getDimension(
+ android.R.dimen.notification_large_icon_height);
+ Drawable d;
+ if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+ // noinspection deprecation (can't help it at this api level)
+ d = context.getResources().getDrawable(resId);
+ } else {
+ d = context.getDrawable(resId);
+ }
+ if (d == null) {
+ return null;
+ }
+ Bitmap b = Bitmap.createBitmap(mLargeIconWidth, mLargeIconHeight, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+ d.setBounds(0, 0, mLargeIconWidth, mLargeIconHeight);
+ d.draw(c);
+ return b;
+ }
+
private Notification getNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+ builder.setSmallIcon(R.drawable.ic_stat_notify_24dp)
+ .setLargeIcon(getBitmap(R.drawable.ic_launcher, getBaseContext()))
+ .setContentTitle(getResources().getQuantityString(R.plurals.passp_cache_notif_n_keys,
+ mPassphraseCache.size(), mPassphraseCache.size()))
+ .setContentText(getString(R.string.passp_cache_notif_click_to_clear));
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- builder.setSmallIcon(R.drawable.ic_launcher)
- .setContentTitle(getString(R.string.app_name))
- .setContentText(String.format(getString(R.string.passp_cache_notif_n_keys),
- mPassphraseCache.size()));
+ NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
- NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
+ inboxStyle.setBigContentTitle(getString(R.string.passp_cache_notif_keys));
- inboxStyle.setBigContentTitle(getString(R.string.passp_cache_notif_keys));
+ // Moves events into the big view
+ for (int i = 0; i < mPassphraseCache.size(); i++) {
+ inboxStyle.addLine(mPassphraseCache.valueAt(i).getPrimaryUserID());
+ }
- // Moves events into the big view
- for (int i = 0; i < mPassphraseCache.size(); i++) {
- inboxStyle.addLine(mPassphraseCache.valueAt(i).getPrimaryUserID());
- }
+ // Moves the big view style object into the notification object.
+ builder.setStyle(inboxStyle);
- // Moves the big view style object into the notification object.
- builder.setStyle(inboxStyle);
-
- // Add purging action
- Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
- intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
- builder.addAction(
- R.drawable.abc_ic_clear_mtrl_alpha,
- getString(R.string.passp_cache_notif_clear),
- PendingIntent.getService(
- getApplicationContext(),
- 0,
- intent,
- PendingIntent.FLAG_UPDATE_CURRENT
- )
- );
- } else {
- // Fallback, since expandable notifications weren't available back then
- builder.setSmallIcon(R.drawable.ic_launcher)
- .setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys),
- mPassphraseCache.size()))
- .setContentText(getString(R.string.passp_cache_notif_click_to_clear));
-
- Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
- intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
-
- builder.setContentIntent(
- PendingIntent.getService(
- getApplicationContext(),
- 0,
- intent,
- PendingIntent.FLAG_UPDATE_CURRENT
- )
- );
- }
+ Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
+ intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
+ PendingIntent clearCachePi = PendingIntent.getService(
+ getApplicationContext(),
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ );
+
+ // Add cache clear PI to normal touch
+ builder.setContentIntent(clearCachePi);
+
+ // Add clear PI action below text
+ builder.addAction(
+ R.drawable.abc_ic_clear_mtrl_alpha,
+ getString(R.string.passp_cache_notif_clear),
+ clearCachePi
+ );
return builder.build();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java
new file mode 100644
index 000000000..d268c3694
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 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 PromoteKeyringParcel implements Parcelable {
+
+ public long mKeyRingId;
+ public byte[] mCardAid;
+ public long[] mSubKeyIds;
+
+ public PromoteKeyringParcel(long keyRingId, byte[] cardAid, long[] subKeyIds) {
+ mKeyRingId = keyRingId;
+ mCardAid = cardAid;
+ mSubKeyIds = subKeyIds;
+ }
+
+ protected PromoteKeyringParcel(Parcel in) {
+ mKeyRingId = in.readLong();
+ mCardAid = in.createByteArray();
+ mSubKeyIds = in.createLongArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mKeyRingId);
+ dest.writeByteArray(mCardAid);
+ dest.writeLongArray(mSubKeyIds);
+ }
+
+ public static final Parcelable.Creator<PromoteKeyringParcel> CREATOR = new Parcelable.Creator<PromoteKeyringParcel>() {
+ @Override
+ public PromoteKeyringParcel createFromParcel(Parcel in) {
+ return new PromoteKeyringParcel(in);
+ }
+
+ @Override
+ public PromoteKeyringParcel[] newArray(int size) {
+ return new PromoteKeyringParcel[size];
+ }
+ };
+} \ No newline at end of file
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 2e0524141..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() {
@@ -95,7 +127,8 @@ public class SaveKeyringParcel implements Parcelable {
}
for (SubkeyChange change : mChangeSubKeys) {
- if (change.mRecertify || change.mFlags != null || change.mExpiry != null) {
+ if (change.mRecertify || change.mFlags != null || change.mExpiry != null
+ || change.mMoveKeyToCard) {
return false;
}
}
@@ -142,6 +175,8 @@ public class SaveKeyringParcel implements Parcelable {
public boolean mRecertify;
// if this flag is true, the subkey should be changed to a stripped key
public boolean mDummyStrip;
+ // if this flag is true, the subkey should be moved to a card
+ public boolean mMoveKeyToCard;
// if this is non-null, the subkey will be changed to a divert-to-card
// key for the given serial number
public byte[] mDummyDivert;
@@ -161,16 +196,16 @@ public class SaveKeyringParcel implements Parcelable {
mExpiry = expiry;
}
- public SubkeyChange(long keyId, boolean dummyStrip, byte[] dummyDivert) {
+ public SubkeyChange(long keyId, boolean dummyStrip, boolean moveKeyToCard) {
this(keyId, null, null);
// these flags are mutually exclusive!
- if (dummyStrip && dummyDivert != null) {
+ if (dummyStrip && moveKeyToCard) {
throw new AssertionError(
- "cannot set strip and divert flags at the same time - this is a bug!");
+ "cannot set strip and keytocard flags at the same time - this is a bug!");
}
mDummyStrip = dummyStrip;
- mDummyDivert = dummyDivert;
+ mMoveKeyToCard = moveKeyToCard;
}
@Override
@@ -179,6 +214,7 @@ public class SaveKeyringParcel implements Parcelable {
out += "mFlags: " + mFlags + ", ";
out += "mExpiry: " + mExpiry + ", ";
out += "mDummyStrip: " + mDummyStrip + ", ";
+ out += "mMoveKeyToCard: " + mMoveKeyToCard + ", ";
out += "mDummyDivert: [" + (mDummyDivert == null ? 0 : mDummyDivert.length) + " bytes]";
return out;
@@ -206,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();
@@ -221,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
@@ -232,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);
@@ -243,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>() {
@@ -270,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/ServiceProgressHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java
index 430d8a49b..d294e5057 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java
@@ -17,8 +17,8 @@
package org.sufficientlysecure.keychain.service;
-import android.app.Activity;
-import android.content.Intent;
+
+import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -27,7 +27,6 @@ import android.support.v4.app.FragmentManager;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
@@ -35,7 +34,7 @@ import org.sufficientlysecure.keychain.util.Log;
public class ServiceProgressHandler extends Handler {
// possible messages sent from this service to handler on ui
- public static enum MessageStatus{
+ public enum MessageStatus {
UNKNOWN,
OKAY,
EXCEPTION,
@@ -44,9 +43,8 @@ public class ServiceProgressHandler extends Handler {
private static final MessageStatus[] values = values();
- public static MessageStatus fromInt(int n)
- {
- if(n < 0 || n >= values.length) {
+ public static MessageStatus fromInt(int n) {
+ if (n < 0 || n >= values.length) {
return UNKNOWN;
} else {
return values[n];
@@ -66,74 +64,51 @@ public class ServiceProgressHandler extends Handler {
public static final String KEYBASE_PRESENCE_URL = "keybase_presence_url";
public static final String KEYBASE_PRESENCE_LABEL = "keybase_presence_label";
- Activity mActivity;
- ProgressDialogFragment mProgressDialogFragment;
+ public static final String TAG_PROGRESS_DIALOG = "progressDialog";
- public ServiceProgressHandler(Activity activity) {
- this.mActivity = activity;
- }
+ FragmentActivity mActivity;
- public ServiceProgressHandler(Activity activity,
- ProgressDialogFragment progressDialogFragment) {
- this.mActivity = activity;
- this.mProgressDialogFragment = progressDialogFragment;
+ public ServiceProgressHandler(FragmentActivity activity) {
+ mActivity = activity;
}
- public ServiceProgressHandler(Activity activity,
- String progressDialogMessage,
- int progressDialogStyle,
- ProgressDialogFragment.ServiceType serviceType) {
- this(activity, progressDialogMessage, progressDialogStyle, false, serviceType);
+ public void showProgressDialog() {
+ showProgressDialog("", ProgressDialog.STYLE_SPINNER, false);
}
- public ServiceProgressHandler(Activity activity,
- String progressDialogMessage,
- int progressDialogStyle,
- boolean cancelable,
- ProgressDialogFragment.ServiceType serviceType) {
- this.mActivity = activity;
- this.mProgressDialogFragment = ProgressDialogFragment.newInstance(
+ public void showProgressDialog(
+ String progressDialogMessage, int progressDialogStyle, boolean cancelable) {
+
+ final ProgressDialogFragment frag = ProgressDialogFragment.newInstance(
progressDialogMessage,
progressDialogStyle,
- cancelable,
- serviceType);
- }
-
- public void showProgressDialog(FragmentActivity activity) {
- if (mProgressDialogFragment == null) {
- return;
- }
+ cancelable);
// TODO: This is a hack!, see
// http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult
- final FragmentManager manager = activity.getSupportFragmentManager();
+ final FragmentManager manager = mActivity.getSupportFragmentManager();
Handler handler = new Handler();
handler.post(new Runnable() {
public void run() {
- mProgressDialogFragment.show(manager, "progressDialog");
+ frag.show(manager, TAG_PROGRESS_DIALOG);
}
});
+
}
@Override
public void handleMessage(Message message) {
Bundle data = message.getData();
- if (mProgressDialogFragment == null) {
- // Log.e(Constants.TAG,
- // "Progress has not been updated because mProgressDialogFragment was null!");
- return;
- }
-
MessageStatus status = MessageStatus.fromInt(message.arg1);
switch (status) {
case OKAY:
- mProgressDialogFragment.dismissAllowingStateLoss();
+ dismissAllowingStateLoss();
break;
case EXCEPTION:
- mProgressDialogFragment.dismissAllowingStateLoss();
+ dismissAllowingStateLoss();
// show error from service
if (data.containsKey(DATA_ERROR)) {
@@ -147,23 +122,25 @@ public class ServiceProgressHandler extends Handler {
case UPDATE_PROGRESS:
if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) {
+ String msg = null;
+ int progress = data.getInt(DATA_PROGRESS);
+ int max = data.getInt(DATA_PROGRESS_MAX);
+
// update progress from service
if (data.containsKey(DATA_MESSAGE)) {
- mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE),
- data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
+ msg = data.getString(DATA_MESSAGE);
} else if (data.containsKey(DATA_MESSAGE_ID)) {
- mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID),
- data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
- } else {
- mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS),
- data.getInt(DATA_PROGRESS_MAX));
+ msg = mActivity.getString(data.getInt(DATA_MESSAGE_ID));
}
+
+ onSetProgress(msg, progress, max);
+
}
break;
case PREVENT_CANCEL:
- mProgressDialogFragment.setPreventCancel(true);
+ setPreventCancel(true);
break;
default:
@@ -171,4 +148,48 @@ public class ServiceProgressHandler extends Handler {
break;
}
}
+
+ private void setPreventCancel(boolean preventCancel) {
+ ProgressDialogFragment progressDialogFragment =
+ (ProgressDialogFragment) mActivity.getSupportFragmentManager()
+ .findFragmentByTag("progressDialog");
+
+ if (progressDialogFragment == null) {
+ return;
+ }
+
+ progressDialogFragment.setPreventCancel(preventCancel);
+ }
+
+ protected void dismissAllowingStateLoss() {
+ ProgressDialogFragment progressDialogFragment =
+ (ProgressDialogFragment) mActivity.getSupportFragmentManager()
+ .findFragmentByTag("progressDialog");
+
+ if (progressDialogFragment == null) {
+ return;
+ }
+
+ progressDialogFragment.dismissAllowingStateLoss();
+ }
+
+
+ protected void onSetProgress(String msg, int progress, int max) {
+
+ ProgressDialogFragment progressDialogFragment =
+ (ProgressDialogFragment) mActivity.getSupportFragmentManager()
+ .findFragmentByTag("progressDialog");
+
+ if (progressDialogFragment == null) {
+ return;
+ }
+
+ if (msg != null) {
+ progressDialogFragment.setProgress(msg, progress, max);
+ } else {
+ progressDialogFragment.setProgress(progress, max);
+ }
+
+ }
+
}
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 3d1ccaca1..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,12 +133,28 @@ 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);
}
+ public void addCryptoData(Map<ByteBuffer, byte[]> cachedSessionKeys) {
+ mCryptoData.putAll(cachedSessionKeys);
+ }
+
+ public ParcelableProxy getParcelableProxy() {
+ return mParcelableProxy;
+ }
+
public Map<ByteBuffer, byte[]> getCryptoData() {
- return Collections.unmodifiableMap(mCryptoData);
+ return mCryptoData;
}
public Date getSignatureTime() {
@@ -138,4 +194,5 @@ public class CryptoInputParcel implements Parcelable {
b.append("}");
return b.toString();
}
+
}
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 535c1e735..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,35 +1,37 @@
package org.sufficientlysecure.keychain.service.input;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-
import android.os.Parcel;
import android.os.Parcelable;
-import org.sufficientlysecure.keychain.Constants.key;
+import org.sufficientlysecure.keychain.util.Passphrase;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
public class RequiredInputParcel implements Parcelable {
public enum RequiredInputType {
- PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT
+ 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;
@@ -39,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;
}
@@ -75,16 +77,27 @@ 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) {
return new RequiredInputParcel(RequiredInputType.NFC_SIGN,
new byte[][] { inputHash }, new int[] { signAlgo },
- signatureTime, null, null);
+ signatureTime, masterKeyId, subKeyId);
}
- public static RequiredInputParcel createNfcDecryptOperation(byte[] inputHash, long subKeyId) {
+ public static RequiredInputParcel createNfcDecryptOperation(
+ long masterKeyId, long subKeyId, byte[] encryptedSessionKey) {
return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT,
- new byte[][] { inputHash }, null, null, null, subKeyId);
+ new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId);
}
public static RequiredInputParcel createRequiredSignPassphrase(
@@ -118,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]);
}
@@ -165,10 +178,10 @@ public class RequiredInputParcel implements Parcelable {
Date mSignatureTime;
ArrayList<Integer> mSignAlgos = new ArrayList<>();
ArrayList<byte[]> mInputHashes = new ArrayList<>();
- Long mMasterKeyId;
- Long mSubKeyId;
+ long mMasterKeyId;
+ long mSubKeyId;
- public NfcSignOperationsBuilder(Date signatureTime, Long masterKeyId, Long subKeyId) {
+ public NfcSignOperationsBuilder(Date signatureTime, long masterKeyId, long subKeyId) {
mSignatureTime = signatureTime;
mMasterKeyId = masterKeyId;
mSubKeyId = subKeyId;
@@ -199,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);
}
@@ -211,4 +224,66 @@ 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[][] 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,
+ inputData, null, null, mMasterKeyId, buf.getLong());
+ }
+
+ public void addSubkey(long subkeyId) {
+ byte[] subKeyId = new byte[8];
+ ByteBuffer buf = ByteBuffer.wrap(subKeyId);
+ buf.putLong(subkeyId).rewind();
+ 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!");
+ }
+ if (input.mType != RequiredInputType.NFC_MOVE_KEY_TO_CARD) {
+ throw new AssertionError("Operation types must match, this is a programming error!");
+ }
+
+ Collections.addAll(mSubkeysToExport, input.mInputData);
+ }
+
+ public boolean isEmpty() {
+ return mSubkeysToExport.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..a3ea8ad9a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java
@@ -0,0 +1,200 @@
+/*
+ * 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.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Locale;
+
+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;
+ private ExportHelper mExportHelper;
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ // we won't get attached to a non-fragment activity, so the cast should be safe
+ mExportHelper = new ExportHelper((FragmentActivity) activity);
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mExportHelper = null;
+ }
+
+ @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) {
+ startBackup(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;
+ }
+
+ startBackup(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;
+ }
+
+ startBackup(true);
+ }
+ }
+
+ private void startBackup(boolean exportSecret) {
+ File filename;
+ String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
+ if (exportSecret) {
+ filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".asc");
+ } else {
+ filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".pub.asc");
+ }
+ mExportHelper.showExportKeysDialog(null, filename, exportSecret);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java
index 016ab5f3c..c5528e40b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java
@@ -30,6 +30,8 @@ public class CertifyFingerprintActivity extends BaseActivity {
protected Uri mDataUri;
+ public static final String EXTRA_ENABLE_WORD_CONFIRM = "enable_word_confirm";
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -40,6 +42,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
finish();
return;
}
+ boolean enableWordConfirm = getIntent().getBooleanExtra(EXTRA_ENABLE_WORD_CONFIRM, false);
setFullScreenDialogClose(new View.OnClickListener() {
@Override
@@ -50,7 +53,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
- startFragment(savedInstanceState, mDataUri);
+ startFragment(savedInstanceState, mDataUri, enableWordConfirm);
}
@Override
@@ -58,7 +61,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
setContentView(R.layout.certify_fingerprint_activity);
}
- private void startFragment(Bundle savedInstanceState, Uri dataUri) {
+ private void startFragment(Bundle savedInstanceState, Uri dataUri, boolean enableWordConfirm) {
// 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.
@@ -67,7 +70,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
}
// Create an instance of the fragment
- CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri);
+ CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri, enableWordConfirm);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java
index a6b8a0e85..552fa34c0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java
@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.database.Cursor;
+import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
@@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.util.ExperimentalWordConfirm;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
@@ -44,23 +46,24 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
static final int REQUEST_CERTIFY = 1;
public static final String ARG_DATA_URI = "uri";
+ public static final String ARG_ENABLE_WORD_CONFIRM = "enable_word_confirm";
private TextView mFingerprint;
+ private TextView mIntro;
private static final int LOADER_ID_UNIFIED = 0;
private Uri mDataUri;
-
- private View mActionNo;
- private View mActionYes;
+ private boolean mEnableWordConfirm;
/**
* Creates new instance of this fragment
*/
- public static CertifyFingerprintFragment newInstance(Uri dataUri) {
+ public static CertifyFingerprintFragment newInstance(Uri dataUri, boolean enableWordConfirm) {
CertifyFingerprintFragment frag = new CertifyFingerprintFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
+ args.putBoolean(ARG_ENABLE_WORD_CONFIRM, enableWordConfirm);
frag.setArguments(args);
@@ -72,18 +75,19 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.certify_fingerprint_fragment, getContainer());
- mActionNo = view.findViewById(R.id.certify_fingerprint_button_no);
- mActionYes = view.findViewById(R.id.certify_fingerprint_button_yes);
+ View actionNo = view.findViewById(R.id.certify_fingerprint_button_no);
+ View actionYes = view.findViewById(R.id.certify_fingerprint_button_yes);
mFingerprint = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint);
+ mIntro = (TextView) view.findViewById(R.id.certify_fingerprint_intro);
- mActionNo.setOnClickListener(new View.OnClickListener() {
+ actionNo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getActivity().finish();
}
});
- mActionYes.setOnClickListener(new View.OnClickListener() {
+ actionYes.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
certify(mDataUri);
@@ -103,6 +107,11 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
getActivity().finish();
return;
}
+ mEnableWordConfirm = getArguments().getBoolean(ARG_ENABLE_WORD_CONFIRM);
+
+ if (mEnableWordConfirm) {
+ mIntro.setText(R.string.certify_fingerprint_text_words);
+ }
loadData(dataUri);
}
@@ -149,10 +158,13 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
switch (loader.getId()) {
case LOADER_ID_UNIFIED: {
if (data.moveToFirst()) {
-
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
- String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
- mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
+
+ if (mEnableWordConfirm) {
+ displayWordConfirm(fingerprintBlob);
+ } else {
+ displayHexConfirm(fingerprintBlob);
+ }
break;
}
@@ -162,6 +174,19 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
setContentShown(true);
}
+ private void displayHexConfirm(byte[] fingerprintBlob) {
+ String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
+ mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
+ }
+
+ private void displayWordConfirm(byte[] fingerprintBlob) {
+ String fingerprint = ExperimentalWordConfirm.getWords(getActivity(), fingerprintBlob);
+
+ mFingerprint.setTextSize(24);
+ mFingerprint.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
+ mFingerprint.setText(fingerprint);
+ }
+
/**
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
* We need to make sure we are no longer using it.
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 51b6e824d..357b445f0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java
@@ -19,15 +19,12 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
-import android.app.ProgressDialog;
import android.content.Intent;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
import android.os.Parcel;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
@@ -52,24 +49,24 @@ 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.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter;
-import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+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.ui.widget.KeySpinner;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
import java.util.ArrayList;
+import java.util.Date;
-
-public class CertifyKeyFragment extends CryptoOperationFragment
+public class CertifyKeyFragment
+ extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult>
implements LoaderManager.LoaderCallbacks<Cursor> {
+ public static final String ARG_CHECK_STATES = "check_states";
+
private CheckBox mUploadKeyCheckbox;
ListView mUserIds;
@@ -77,8 +74,6 @@ public class CertifyKeyFragment extends CryptoOperationFragment
private long[] mPubMasterKeyIds;
- private long mSignMasterKeyId = Constants.key.none;
-
public static final String[] USER_IDS_PROJECTION = new String[]{
UserPackets._ID,
UserPackets.MASTER_KEY_ID,
@@ -94,7 +89,6 @@ public class CertifyKeyFragment extends CryptoOperationFragment
private static final int INDEX_IS_REVOKED = 4;
private MultiUserIdsAdapter mUserIdsAdapter;
- private Messenger mPassthroughMessenger;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
@@ -107,24 +101,30 @@ public class CertifyKeyFragment extends CryptoOperationFragment
return;
}
- mPassthroughMessenger = getActivity().getIntent().getParcelableExtra(
- KeychainIntentService.EXTRA_MESSENGER);
- mPassthroughMessenger = null; // TODO remove, development hack
-
- // preselect certify key id if given
- long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
- if (certifyKeyId != Constants.key.none) {
- try {
- CachedPublicKeyRing key = (new ProviderHelper(getActivity())).getCachedPublicKeyRing(certifyKeyId);
- if (key.canCertify()) {
- mCertifyKeySpinner.setSelectedKeyId(certifyKeyId);
+ ArrayList<Boolean> checkedStates;
+ if (savedInstanceState != null) {
+ checkedStates = (ArrayList<Boolean>) savedInstanceState.getSerializable(ARG_CHECK_STATES);
+ // key spinner and the checkbox keep their own state
+ } else {
+ checkedStates = null;
+
+ // preselect certify key id if given
+ long certifyKeyId = getActivity().getIntent()
+ .getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
+ if (certifyKeyId != Constants.key.none) {
+ try {
+ CachedPublicKeyRing key = (new ProviderHelper(getActivity())).getCachedPublicKeyRing(certifyKeyId);
+ if (key.canCertify()) {
+ mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId);
+ }
+ } catch (PgpKeyNotFoundException e) {
+ Log.e(Constants.TAG, "certify certify check failed", e);
}
- } catch (PgpKeyNotFoundException e) {
- Log.e(Constants.TAG, "certify certify check failed", e);
}
+
}
- mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0);
+ mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates);
mUserIds.setAdapter(mUserIdsAdapter);
mUserIds.setDividerHeight(0);
@@ -138,6 +138,15 @@ public class CertifyKeyFragment extends CryptoOperationFragment
}
@Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ ArrayList<Boolean> states = mUserIdsAdapter.getCheckStates();
+ // no proper parceling method available :(
+ outState.putSerializable(ARG_CHECK_STATES, states);
+ }
+
+ @Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.certify_key_fragment, null);
@@ -148,26 +157,20 @@ public class CertifyKeyFragment extends CryptoOperationFragment
// 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);
- mCertifyKeySpinner.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
- @Override
- public void onKeyChanged(long masterKeyId) {
- mSignMasterKeyId = masterKeyId;
- }
- });
-
View vCertifyButton = view.findViewById(R.id.certify_key_certify_button);
vCertifyButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- if (mSignMasterKeyId == Constants.key.none) {
+ long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId();
+ if (selectedKeyId == Constants.key.none) {
Notify.create(getActivity(), getString(R.string.select_key_to_certify),
Notify.Style.ERROR).show();
} else {
- cryptoOperation(new CryptoInputParcel());
+ cryptoOperation(new CryptoInputParcel(new Date()));
}
}
});
@@ -298,82 +301,42 @@ public class CertifyKeyFragment extends CryptoOperationFragment
}
@Override
- protected void cryptoOperation(CryptoInputParcel cryptoInput) {
+ public CertifyActionsParcel createOperationInput() {
+
// Bail out if there is not at least one user id selected
ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions();
if (certifyActions.isEmpty()) {
Notify.create(getActivity(), "No identities selected!",
Notify.Style.ERROR).show();
- return;
- }
-
- Bundle data = new Bundle();
- {
- // fill values for this action
- CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId);
- parcel.mCertifyActions.addAll(certifyActions);
-
- data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
- data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
- if (mUploadKeyCheckbox.isChecked()) {
- String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
- data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
- }
+ return null;
}
- // Send all information needed to service to sign key in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+ long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId();
- if (mPassthroughMessenger != null) {
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, mPassthroughMessenger);
- } else {
+ // fill values for this action
+ CertifyActionsParcel actionsParcel = new CertifyActionsParcel(selectedKeyId);
+ actionsParcel.mCertifyActions.addAll(certifyActions);
- // Message is received after signing is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_certifying),
- ProgressDialog.STYLE_SPINNER,
- true,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by KeychainIntentCryptoServiceHandler first
- super.handleMessage(message);
-
- // handle pending messages
- if (handlePendingMessage(message)) {
- return;
- }
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- Bundle data = message.getData();
-
- CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
-
- Intent intent = new Intent();
- intent.putExtra(CertifyResult.EXTRA_RESULT, result);
- getActivity().setResult(Activity.RESULT_OK, intent);
- getActivity().finish();
- }
- }
- };
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- saveHandler.showProgressDialog(getActivity());
+ if (mUploadKeyCheckbox.isChecked()) {
+ actionsParcel.keyServerUri = Preferences.getPreferences(getActivity())
+ .getPreferredKeyserver();
}
- // start service with intent
- getActivity().startService(intent);
+ // cached for next cryptoOperation loop
+ cacheActionsParcel(actionsParcel);
- if (mPassthroughMessenger != null) {
- getActivity().setResult(Activity.RESULT_OK);
- getActivity().finish();
- }
+ return actionsParcel;
+ }
+
+ @Override
+ public void onQueuedOperationSuccess(CertifyResult result) {
+ // protected by Queueing*Fragment
+ Activity activity = getActivity();
+
+ Intent intent = new Intent();
+ intent.putExtra(CertifyResult.EXTRA_RESULT, result);
+ activity.setResult(Activity.RESULT_OK, intent);
+ activity.finish();
}
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 11ba3e287..ff5fb7cca 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java
@@ -17,26 +17,27 @@
package org.sufficientlysecure.keychain.ui;
-import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
import android.support.v4.app.FragmentActivity;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
+import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
/**
* We can not directly create a dialog on the application context.
* This activity encapsulates a DialogFragment to emulate a dialog.
*/
-public class ConsolidateDialogActivity extends FragmentActivity {
+public class ConsolidateDialogActivity extends FragmentActivity
+ implements CryptoOperationHelper.Callback<ConsolidateInputParcel, ConsolidateResult> {
public static final String EXTRA_CONSOLIDATE_RECOVERY = "consolidate_recovery";
+ private CryptoOperationHelper<ConsolidateInputParcel, ConsolidateResult> mConsolidateOpHelper;
+ private boolean mRecovery;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -49,55 +50,45 @@ public class ConsolidateDialogActivity extends FragmentActivity {
}
private void consolidateRecovery(boolean recovery) {
- // Message is received after importing is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- this,
- getString(R.string.progress_importing),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- /* don't care about the results (for now?)
-
- // get returned data bundle
- Bundle returnData = message.getInputData();
- if (returnData == null) {
- return;
- }
- final ConsolidateResult result =
- returnData.getParcelable(KeychainIntentService.RESULT_CONSOLIDATE);
- if (result == null) {
- return;
- }
- result.createNotify(ConsolidateDialogActivity.this).show();
- */
-
- ConsolidateDialogActivity.this.finish();
- }
- }
- };
-
- // Send all information needed to service to import key in other thread
- Intent intent = new Intent(this, KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE);
-
- // fill values for this action
- Bundle data = new Bundle();
- data.putBoolean(KeychainIntentService.CONSOLIDATE_RECOVERY, recovery);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- saveHandler.showProgressDialog(this);
-
- // start service with intent
- startService(intent);
+
+ mRecovery = recovery;
+
+ mConsolidateOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_importing);
+ mConsolidateOpHelper.cryptoOperation();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (mConsolidateOpHelper != null) {
+ mConsolidateOpHelper.handleActivityResult(requestCode, resultCode, data);
+ }
}
+ @Override
+ public ConsolidateInputParcel createOperationInput() {
+ return new ConsolidateInputParcel(mRecovery);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(ConsolidateResult result) {
+ // don't care about result (for now?)
+ ConsolidateDialogActivity.this.finish();
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+
+ }
+
+ @Override
+ public void onCryptoOperationError(ConsolidateResult result) {
+ // don't care about result (for now?)
+ ConsolidateDialogActivity.this.finish();
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
}
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 e0b728bd4..579a001cb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
+import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
@@ -29,7 +30,9 @@ 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 org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException;
import java.util.ArrayList;
@@ -41,6 +44,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_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";
@@ -53,13 +59,33 @@ public class CreateKeyActivity extends BaseNfcActivity {
ArrayList<String> mAdditionalEmails;
Passphrase mPassphrase;
boolean mFirstTime;
+ boolean mCreateYubiKey;
+ Passphrase mYubiKeyPin;
+ Passphrase mYubiKeyAdminPin;
Fragment mCurrentFragment;
+
+ byte[] mScannedFingerprints;
+ byte[] mNfcAid;
+ String mNfcUserId;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ // 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())) {
+
+ handleIntentInBackground(getIntent());
+
+ setTitle(R.string.title_manage_my_keys);
+
+ // done
+ return;
+ }
+
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
@@ -68,6 +94,9 @@ public class CreateKeyActivity extends BaseNfcActivity {
mAdditionalEmails = savedInstanceState.getStringArrayList(EXTRA_ADDITIONAL_EMAILS);
mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE);
mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME);
+ 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 {
@@ -77,23 +106,36 @@ public class CreateKeyActivity extends BaseNfcActivity {
mName = intent.getStringExtra(EXTRA_NAME);
mEmail = intent.getStringExtra(EXTRA_EMAIL);
mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
+ mCreateYubiKey = intent.getBooleanExtra(EXTRA_CREATE_YUBI_KEY, false);
if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) {
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
- Fragment frag2 = CreateKeyYubiKeyImportFragment.createInstance(
- nfcFingerprints, nfcAid, nfcUserId);
- loadFragment(frag2, FragAction.START);
-
- setTitle(R.string.title_import_keys);
+ if (containsKeys(nfcFingerprints)) {
+ Fragment frag = CreateYubiKeyImportFragment.newInstance(
+ nfcFingerprints, nfcAid, nfcUserId);
+ loadFragment(frag, FragAction.START);
+
+ setTitle(R.string.title_import_keys);
+ } else {
+// 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
return;
- } else {
- CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance();
- loadFragment(frag, FragAction.START);
}
+ // normal key creation
+ CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance();
+ loadFragment(frag, FragAction.START);
}
if (mFirstTime) {
@@ -106,35 +148,63 @@ 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();
-
- try {
- long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints);
- CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
- ring.getMasterKeyId();
+ mScannedFingerprints = nfcGetFingerprints();
+ mNfcAid = nfcGetAid();
+ mNfcUserId = nfcGetUserId();
+ }
- 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);
- startActivity(intent);
- finish();
+ @Override
+ protected void onNfcPostExecute() throws IOException {
+ if (mCurrentFragment instanceof NfcListenerFragment) {
+ ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute();
+ return;
+ }
- } catch (PgpKeyNotFoundException e) {
- Fragment frag = CreateKeyYubiKeyImportFragment.createInstance(
- scannedFingerprints, nfcAid, userId);
- loadFragment(frag, FragAction.TO_RIGHT);
+ if (containsKeys(mScannedFingerprints)) {
+ try {
+ 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, mNfcAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mScannedFingerprints);
+ startActivity(intent);
+ finish();
+
+ } catch (PgpKeyNotFoundException e) {
+ Fragment frag = CreateYubiKeyImportFragment.newInstance(
+ mScannedFingerprints, mNfcAid, mNfcUserId);
+ loadFragment(frag, FragAction.TO_RIGHT);
+ }
+ } else {
+// 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) {
+ // If all fingerprint bytes are 0, the card contains no keys.
+ boolean cardContainsKeys = false;
+ for (byte b : scannedFingerprints) {
+ if (b != 0) {
+ cardContainsKeys = true;
+ break;
+ }
+ }
+ return cardContainsKeys;
}
@Override
@@ -146,6 +216,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_CREATE_YUBI_KEY, mCreateYubiKey);
+ outState.putParcelable(EXTRA_YUBI_KEY_PIN, mYubiKeyPin);
+ outState.putParcelable(EXTRA_YUBI_KEY_ADMIN_PIN, mYubiKeyAdminPin);
}
@Override
@@ -153,7 +226,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
setContentView(R.layout.create_key_activity);
}
- public static enum FragAction {
+ public enum FragAction {
START,
TO_RIGHT,
TO_LEFT
@@ -190,7 +263,19 @@ public class CreateKeyActivity extends BaseNfcActivity {
}
interface NfcListenerFragment {
- public void onNfcPerform() throws IOException;
+ void doNfcInBackground() throws IOException;
+ void onNfcPostExecute() throws IOException;
}
+ @Override
+ public void finish() {
+ if (mFirstTime) {
+ Preferences prefs = Preferences.getPreferences(this);
+ prefs.setFirstTime(false);
+ Intent intent = new Intent(this, MainActivity.class);
+ startActivity(intent);
+ }
+
+ super.finish();
+ }
}
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 b0a13c897..739eb3e35 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014-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,13 +17,15 @@
package org.sufficientlysecure.keychain.ui;
+
+import java.util.Date;
+import java.util.Iterator;
+
import android.app.Activity;
-import android.app.ProgressDialog;
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.view.LayoutInflater;
import android.view.View;
@@ -35,27 +37,28 @@ import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
+import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+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.dialog.ProgressDialogFragment;
+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;
- CreateKeyActivity mCreateKeyActivity;
-
TextView mNameEdit;
TextView mEmailEdit;
CheckBox mUploadCheckbox;
@@ -66,11 +69,18 @@ public class CreateKeyFinalFragment extends Fragment {
SaveKeyringParcel mSaveKeyringParcel;
- /**
- * Creates new instance of this fragment
- */
+ private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper;
+ 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);
@@ -90,11 +100,13 @@ public class CreateKeyFinalFragment extends Fragment {
mEditText = (TextView) view.findViewById(R.id.create_key_edit_text);
mEditButton = view.findViewById(R.id.create_key_edit_button);
+ CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity();
+
// set values
- mNameEdit.setText(mCreateKeyActivity.mName);
- if (mCreateKeyActivity.mAdditionalEmails != null && mCreateKeyActivity.mAdditionalEmails.size() > 0) {
- String emailText = mCreateKeyActivity.mEmail + ", ";
- Iterator<?> it = mCreateKeyActivity.mAdditionalEmails.iterator();
+ mNameEdit.setText(createKeyActivity.mName);
+ if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) {
+ String emailText = createKeyActivity.mEmail + ", ";
+ Iterator<?> it = createKeyActivity.mAdditionalEmails.iterator();
while (it.hasNext()) {
Object next = it.next();
emailText += next;
@@ -104,7 +116,7 @@ public class CreateKeyFinalFragment extends Fragment {
}
mEmailEdit.setText(emailText);
} else {
- mEmailEdit.setText(mCreateKeyActivity.mEmail);
+ mEmailEdit.setText(createKeyActivity.mEmail);
}
mCreateButton.setOnClickListener(new View.OnClickListener() {
@@ -117,7 +129,10 @@ public class CreateKeyFinalFragment extends Fragment {
mBackButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
+ CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity();
+ if (createKeyActivity != null) {
+ createKeyActivity.loadFragment(null, FragAction.TO_LEFT);
+ }
}
});
@@ -130,17 +145,26 @@ public class CreateKeyFinalFragment extends Fragment {
}
});
- return view;
- }
+ // If this is a debug build, don't upload by default
+ if (Constants.DEBUG) {
+ mUploadCheckbox.setChecked(false);
+ }
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mCreateKeyActivity = (CreateKeyActivity) getActivity();
+ return view;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mCreateOpHelper != null) {
+ mCreateOpHelper.handleActivityResult(requestCode, resultCode, data);
+ }
+ if (mMoveToCardOpHelper != null) {
+ mMoveToCardOpHelper.handleActivityResult(requestCode, resultCode, data);
+ }
+ if (mUploadOpHelper != null) {
+ mUploadOpHelper.handleActivityResult(requestCode, resultCode, data);
+ }
+
switch (requestCode) {
case REQUEST_EDIT_KEY: {
if (resultCode == Activity.RESULT_OK) {
@@ -159,147 +183,283 @@ public class CreateKeyFinalFragment extends Fragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- mCreateKeyActivity = (CreateKeyActivity) getActivity();
+ CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity();
if (mSaveKeyringParcel == null) {
mSaveKeyringParcel = new SaveKeyringParcel();
- mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
- Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L));
- mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
- Algorithm.RSA, 4096, null, KeyFlags.SIGN_DATA, 0L));
- mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
- Algorithm.RSA, 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
+
+ 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,
+ 2048, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
+ mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
+ 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));
+ mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
+ 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(mCreateKeyActivity.mName, mCreateKeyActivity.mEmail, null)
+ new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null)
);
mSaveKeyringParcel.mAddUserIds.add(userId);
mSaveKeyringParcel.mChangePrimaryUserId = userId;
- if (mCreateKeyActivity.mAdditionalEmails != null
- && mCreateKeyActivity.mAdditionalEmails.size() > 0) {
- for (String email : mCreateKeyActivity.mAdditionalEmails) {
+ if (createKeyActivity.mAdditionalEmails != null
+ && createKeyActivity.mAdditionalEmails.size() > 0) {
+ for (String email : createKeyActivity.mAdditionalEmails) {
String thisUserId = KeyRing.createUserId(
- new KeyRing.UserId(mCreateKeyActivity.mName, email, null)
+ new KeyRing.UserId(createKeyActivity.mName, email, null)
);
mSaveKeyringParcel.mAddUserIds.add(thisUserId);
}
}
- mSaveKeyringParcel.mNewUnlock = mCreateKeyActivity.mPassphrase != null
- ? new ChangeUnlockParcel(mCreateKeyActivity.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() {
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_EDIT_KEYRING);
-
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_building_key),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
- if (returnData == null) {
- return;
- }
- final EditKeyResult result =
- returnData.getParcelable(OperationResult.EXTRA_RESULT);
- if (result == null) {
- Log.e(Constants.TAG, "result == null");
- return;
- }
-
- if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) {
- // result will be displayed after upload
- uploadKey(result);
- } else {
- Intent data = new Intent();
- data.putExtra(OperationResult.EXTRA_RESULT, result);
- getActivity().setResult(Activity.RESULT_OK, data);
- getActivity().finish();
- }
+ 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>() {
+ @Override
+ public SaveKeyringParcel createOperationInput() {
+ return mSaveKeyringParcel;
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(EditKeyResult result) {
+
+ if (createYubiKey) {
+ moveToCard(result);
+ return;
}
+
+ if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) {
+ // result will be displayed after upload
+ uploadKey(result);
+ return;
+ }
+
+ finishWithResult(result);
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+
+ }
+
+ @Override
+ public void onCryptoOperationError(EditKeyResult result) {
+ displayResult(result);
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
}
};
- // fill values for this action
- Bundle data = new Bundle();
+ mCreateOpHelper = new CryptoOperationHelper<>(1, this, createKeyCallback, R.string.progress_building_key);
+ mCreateOpHelper.cryptoOperation();
+ }
- // get selected key entries
- data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
+ private void displayResult(EditKeyResult result) {
+ Activity activity = getActivity();
+ if (activity == null) {
+ mQueuedDisplayResult = result;
+ return;
+ }
+ result.createNotify(activity).show();
+ }
+
+ private void moveToCard(final EditKeyResult saveKeyResult) {
+ 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) {
+ Log.e(Constants.TAG, "Key that should be moved to YubiKey not found in database!");
+ return;
+ }
+
+ // 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
+ );
+ try {
+ while (cursor != null && cursor.moveToNext()) {
+ long subkeyId = cursor.getLong(0);
+ changeKeyringParcel.getOrCreateSubkeyChange(subkeyId).mMoveKeyToCard = true;
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+ // define new PIN and Admin PIN for the card
+ changeKeyringParcel.mCardPin = activity.mYubiKeyPin;
+ changeKeyringParcel.mCardAdminPin = activity.mYubiKeyAdminPin;
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+ CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> callback
+ = new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() {
- saveHandler.showProgressDialog(getActivity());
+ @Override
+ public SaveKeyringParcel createOperationInput() {
+ return changeKeyringParcel;
+ }
- getActivity().startService(intent);
+ @Override
+ public void onCryptoOperationSuccess(EditKeyResult result) {
+ handleResult(result);
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+
+ }
+
+ @Override
+ public void onCryptoOperationError(EditKeyResult result) {
+ handleResult(result);
+ }
+
+ public void handleResult(EditKeyResult result) {
+ // merge logs of createKey with moveToCard
+ saveKeyResult.getLog().add(result, 0);
+
+ if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) {
+ // result will be displayed after upload
+ uploadKey(saveKeyResult);
+ return;
+ }
+
+ finishWithResult(saveKeyResult);
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
+ };
+
+
+ mMoveToCardOpHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_modify);
+ mMoveToCardOpHelper.cryptoOperation(new CryptoInputParcel(new Date()));
}
- // TODO move into EditKeyOperation
private void uploadKey(final EditKeyResult saveKeyResult) {
- // Send all information needed to service to upload key in other thread
- final Intent intent = new Intent(getActivity(), KeychainIntentService.class);
-
- intent.setAction(KeychainIntentService.ACTION_UPLOAD_KEYRING);
+ 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
- Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(
- saveKeyResult.mMasterKeyId);
- intent.setData(blobUri);
+ final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(saveKeyResult.mMasterKeyId);
+ // upload to favorite keyserver
+ final String keyserver = Preferences.getPreferences(activity).getPreferredKeyserver();
- // fill values for this action
- Bundle data = new Bundle();
+ CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> callback
+ = new CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult>() {
+
+ @Override
+ public ExportKeyringParcel createOperationInput() {
+ return new ExportKeyringParcel(keyserver, blobUri);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(ExportResult result) {
+ handleResult(result);
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
- // upload to favorite keyserver
- String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
- data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
-
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_uploading),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // TODO: upload operation needs a result!
- // TODO: then combine these results
- //if (result.getResult() == OperationResultParcel.RESULT_OK) {
- //Notify.create(getActivity(), R.string.key_send_success,
- //Notify.Style.OK).show();
-
- Intent data = new Intent();
- data.putExtra(OperationResult.EXTRA_RESULT, saveKeyResult);
- getActivity().setResult(Activity.RESULT_OK, data);
- getActivity().finish();
- }
+ }
+
+ @Override
+ public void onCryptoOperationError(ExportResult result) {
+ handleResult(result);
+ }
+
+ public void handleResult(ExportResult result) {
+ saveKeyResult.getLog().add(result, 0);
+ finishWithResult(saveKeyResult);
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
}
};
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+ mUploadOpHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_uploading);
+ mUploadOpHelper.cryptoOperation();
+ }
- // show progress dialog
- saveHandler.showProgressDialog(getActivity());
+ public void finishWithResult(OperationResult result) {
+ Activity activity = getActivity();
+ if (activity == null) {
+ mQueuedFinishResult = result;
+ return;
+ }
- // start service with intent
- getActivity().startService(intent);
+ 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..68ec0e8c8 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);
}
});
@@ -98,17 +98,10 @@ public class CreateKeyStartFragment extends Fragment {
mSkipOrCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (mCreateKeyActivity.mFirstTime) {
- Preferences prefs = Preferences.getPreferences(mCreateKeyActivity);
- prefs.setFirstTime(false);
- Intent intent = new Intent(mCreateKeyActivity, MainActivity.class);
- startActivity(intent);
- mCreateKeyActivity.finish();
- } else {
- // just finish activity and return data
+ if (!mCreateKeyActivity.mFirstTime) {
mCreateKeyActivity.setResult(Activity.RESULT_CANCELED);
- mCreateKeyActivity.finish();
}
+ mCreateKeyActivity.finish();
}
});
@@ -124,9 +117,6 @@ public class CreateKeyStartFragment extends Fragment {
if (mCreateKeyActivity.mFirstTime) {
Preferences prefs = Preferences.getPreferences(mCreateKeyActivity);
prefs.setFirstTime(false);
- Intent intent = new Intent(mCreateKeyActivity, MainActivity.class);
- intent.putExtras(data);
- startActivity(intent);
mCreateKeyActivity.finish();
} else {
// just finish activity and return data
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java
new file mode 100644
index 000000000..5b13dc88e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
+
+public class CreateYubiKeyBlankFragment extends Fragment {
+
+ CreateKeyActivity mCreateKeyActivity;
+ View mBackButton;
+ View mNextButton;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static CreateYubiKeyBlankFragment newInstance() {
+ CreateYubiKeyBlankFragment frag = new CreateYubiKeyBlankFragment();
+
+ 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_blank_fragment, container, false);
+
+ mBackButton = view.findViewById(R.id.create_key_back_button);
+ mNextButton = view.findViewById(R.id.create_key_next_button);
+
+ mBackButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (getFragmentManager().getBackStackEntryCount() == 0) {
+ getActivity().setResult(Activity.RESULT_CANCELED);
+ getActivity().finish();
+ } else {
+ mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
+ }
+ }
+ });
+ 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() {
+ 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 f8d79d33b..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,15 +17,14 @@
package org.sufficientlysecure.keychain.ui;
+
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import android.app.Activity;
-import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
@@ -36,19 +35,19 @@ import android.widget.TextView;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
-import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+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.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Preferences;
-public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListenerFragment {
+public class CreateYubiKeyImportFragment
+ extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult>
+ implements NfcListenerFragment {
private static final String ARG_FINGERPRINT = "fingerprint";
public static final String ARG_AID = "aid";
@@ -57,7 +56,6 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe
CreateKeyActivity mCreateKeyActivity;
private byte[] mNfcFingerprints;
- private long mNfcMasterKeyId;
private byte[] mNfcAid;
private String mNfcUserId;
private String mNfcFingerprint;
@@ -65,9 +63,13 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe
private TextView vSerNo;
private TextView vUserId;
- public static Fragment createInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) {
+ // for CryptoOperationFragment key import
+ private String mKeyserver;
+ private ArrayList<ParcelableKeyRing> mKeyList;
+
+ 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);
@@ -88,14 +90,15 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe
mNfcAid = args.getByteArray(ARG_AID);
mNfcUserId = args.getString(ARG_USER_ID);
- mNfcMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
- mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
+ byte[] fp = new byte[20];
+ ByteBuffer.wrap(fp).put(mNfcFingerprints, 0, 20);
+ mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(fp);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.create_yubikey_import_fragment, container, false);
+ View view = inflater.inflate(R.layout.create_yubi_key_import_fragment, container, false);
vSerNo = (TextView) view.findViewById(R.id.yubikey_serno);
vUserId = (TextView) view.findViewById(R.id.yubikey_userid);
@@ -164,7 +167,7 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe
if (!mNfcUserId.isEmpty()) {
vUserId.setText(getString(R.string.yubikey_key_holder, mNfcUserId));
} else {
- vUserId.setText(getString(R.string.yubikey_key_holder_unset));
+ vUserId.setText(getString(R.string.yubikey_key_holder_not_set));
}
}
@@ -175,94 +178,68 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe
public void importKey() {
- // Message is received after decrypting is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_importing),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT
- ) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
-
- ImportKeyResult result =
- returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
-
- long[] masterKeyIds = result.getImportedMasterKeyIds();
-
- // TODO handle masterKeyIds.length != 1...? sorta outlandish scenario
-
- if (!result.success() || masterKeyIds.length == 0) {
- result.createNotify(getActivity()).show();
- return;
- }
-
- Intent intent = new Intent(getActivity(), 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]));
- intent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
- intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints);
- startActivity(intent);
- getActivity().finish();
-
- }
-
- }
- };
-
- // Send all information needed to service to decrypt in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
-
- // fill values for this action
- Bundle data = new Bundle();
-
- intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
-
- String hexFp = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
- keyList.add(new ParcelableKeyRing(hexFp, null, null));
- data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyList);
+ keyList.add(new ParcelableKeyRing(mNfcFingerprint, null, null));
+ mKeyList = keyList;
{
Preferences prefs = Preferences.getPreferences(getActivity());
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
- data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
+ mKeyserver = cloudPrefs.keyserver;
}
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- saveHandler.showProgressDialog(getActivity());
+ super.setProgressMessageResource(R.string.progress_importing);
- // start service with intent
- getActivity().startService(intent);
+ super.cryptoOperation();
}
@Override
- public void onNfcPerform() throws IOException {
+ public void doNfcInBackground() throws IOException {
mNfcFingerprints = mCreateKeyActivity.nfcGetFingerprints();
mNfcAid = mCreateKeyActivity.nfcGetAid();
mNfcUserId = mCreateKeyActivity.nfcGetUserId();
- mNfcMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
- mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
+ byte[] fp = new byte[20];
+ ByteBuffer.wrap(fp).put(mNfcFingerprints, 0, 20);
+ mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(fp);
+ }
+
+ @Override
+ public void onNfcPostExecute() throws IOException {
setData();
+
refreshSearch();
+ }
+
+ @Override
+ public ImportKeyringParcel createOperationInput() {
+ return new ImportKeyringParcel(mKeyList, mKeyserver);
+ }
+
+ @Override
+ public void onQueuedOperationSuccess(ImportKeyResult result) {
+ long[] masterKeyIds = result.getImportedMasterKeyIds();
+ if (masterKeyIds.length == 0) {
+ super.onCryptoOperationError(result);
+ return;
+ }
+ // 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]));
+ intent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints);
+ startActivity(intent);
+ 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 0b8586c0a..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,14 +28,14 @@ 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;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.create_yubikey_wait_fragment, container, false);
+ View view = inflater.inflate(R.layout.create_yubi_key_wait_fragment, container, false);
mBackButton = view.findViewById(R.id.create_key_back_button);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
new file mode 100644
index 000000000..881190ae2
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.widget.Toast;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
+import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+
+
+public class DecryptActivity extends BaseActivity {
+
+ /* Intents */
+ public static final String ACTION_DECRYPT_FROM_CLIPBOARD = "DECRYPT_DATA_CLIPBOARD";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setFullScreenDialogClose(Activity.RESULT_CANCELED, false);
+
+ // Handle intent actions
+ handleActions(savedInstanceState, getIntent());
+ }
+
+ @Override
+ protected void initLayout() {
+ setContentView(R.layout.decrypt_files_activity);
+ }
+
+ /**
+ * Handles all actions with this intent
+ */
+ private void handleActions(Bundle savedInstanceState, Intent intent) {
+
+ // No need to initialize fragments if we are just being restored
+ if (savedInstanceState != null) {
+ return;
+ }
+
+ ArrayList<Uri> uris = new ArrayList<>();
+
+ String action = intent.getAction();
+
+ if (action == null) {
+ Toast.makeText(this, "Error: No action specified!", Toast.LENGTH_LONG).show();
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ try {
+
+ switch (action) {
+ case Intent.ACTION_SEND: {
+ // When sending to Keychain Decrypt via share menu
+ // Binary via content provider (could also be files)
+ // override uri to get stream from send
+ if (intent.hasExtra(Intent.EXTRA_STREAM)) {
+ uris.add(intent.<Uri>getParcelableExtra(Intent.EXTRA_STREAM));
+ } else if (intent.hasExtra(Intent.EXTRA_TEXT)) {
+ String text = intent.getStringExtra(Intent.EXTRA_TEXT);
+ Uri uri = readToTempFile(text);
+ if (uri != null) {
+ uris.add(uri);
+ }
+ }
+
+ break;
+ }
+
+ case Intent.ACTION_SEND_MULTIPLE: {
+ if (intent.hasExtra(Intent.EXTRA_STREAM)) {
+ uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ } else if (intent.hasExtra(Intent.EXTRA_TEXT)) {
+ for (String text : intent.getStringArrayListExtra(Intent.EXTRA_TEXT)) {
+ Uri uri = readToTempFile(text);
+ if (uri != null) {
+ uris.add(uri);
+ }
+ }
+ }
+
+ break;
+ }
+
+ case ACTION_DECRYPT_FROM_CLIPBOARD: {
+ ClipboardManager clipMan = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ if (clipMan == null) {
+ break;
+ }
+
+ ClipData clip = clipMan.getPrimaryClip();
+ if (clip == null) {
+ break;
+ }
+
+ // check if data is available as uri
+ Uri uri = null;
+ for (int i = 0; i < clip.getItemCount(); i++) {
+ ClipData.Item item = clip.getItemAt(i);
+ Uri itemUri = item.getUri();
+ if (itemUri != null) {
+ uri = itemUri;
+ break;
+ }
+ }
+
+ // otherwise, coerce to text (almost always possible) and work from there
+ if (uri == null) {
+ String text = clip.getItemAt(0).coerceToText(this).toString();
+ uri = readToTempFile(text);
+ }
+ if (uri != null) {
+ uris.add(uri);
+ }
+
+ break;
+ }
+
+ // for everything else, just work on the intent data
+ case OpenKeychainIntents.DECRYPT_DATA:
+ case Intent.ACTION_VIEW:
+ default:
+ uris.add(intent.getData());
+
+ }
+
+ } catch (IOException e) {
+ Toast.makeText(this, R.string.error_reading_text, Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+
+ // Definitely need a data uri with the decrypt_data intent
+ if (uris.isEmpty()) {
+ Toast.makeText(this, "No data to decrypt!", Toast.LENGTH_LONG).show();
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ displayListFragment(uris);
+
+ }
+
+ @Nullable public Uri readToTempFile(String text) throws IOException {
+ Uri tempFile = TemporaryStorageProvider.createFile(this);
+ OutputStream outStream = getContentResolver().openOutputStream(tempFile);
+
+ // clean up ascii armored message, fixing newlines and stuff
+ String cleanedText = PgpHelper.getPgpContent(text);
+ if (cleanedText == null) {
+ return null;
+ }
+
+ // if cleanup didn't work, just try the raw data
+ outStream.write(text.getBytes());
+ outStream.close();
+ return tempFile;
+ }
+
+ public void displayListFragment(ArrayList<Uri> inputUris) {
+
+ DecryptListFragment frag = DecryptListFragment.newInstance(inputUris);
+
+ FragmentManager fragMan = getSupportFragmentManager();
+
+ FragmentTransaction trans = fragMan.beginTransaction();
+ trans.replace(R.id.decrypt_files_fragment_container, frag);
+
+ // if there already is a fragment, allow going back to that. otherwise, we're top level!
+ if (fragMan.getFragments() != null && !fragMan.getFragments().isEmpty()) {
+ trans.addToBackStack("list");
+ }
+
+ trans.commit();
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java
deleted file mode 100644
index c9a590c5b..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package org.sufficientlysecure.keychain.ui;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.View;
-
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
-import org.sufficientlysecure.keychain.ui.base.BaseActivity;
-import org.sufficientlysecure.keychain.util.Log;
-
-public class DecryptFilesActivity extends BaseActivity {
-
- /* Intents */
- public static final String ACTION_DECRYPT_DATA = OpenKeychainIntents.DECRYPT_DATA;
-
- // intern
- public static final String ACTION_DECRYPT_DATA_OPEN = Constants.INTENT_PREFIX + "DECRYPT_DATA_OPEN";
-
- DecryptFilesFragment mFragment;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setFullScreenDialogClose(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- setResult(Activity.RESULT_CANCELED);
- finish();
- }
- }, false);
-
- // Handle intent actions
- handleActions(savedInstanceState, getIntent());
- }
-
- @Override
- protected void initLayout() {
- setContentView(R.layout.decrypt_files_activity);
- }
-
- /**
- * Handles all actions with this intent
- *
- * @param intent
- */
- private void handleActions(Bundle savedInstanceState, Intent intent) {
- String action = intent.getAction();
- String type = intent.getType();
- Uri uri = intent.getData();
-
- Bundle mFileFragmentBundle = new Bundle();
-
- /*
- * Android's Action
- */
- if (Intent.ACTION_SEND.equals(action) && type != null) {
- // When sending to Keychain Decrypt via share menu
- // Binary via content provider (could also be files)
- // override uri to get stream from send
- uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
- action = ACTION_DECRYPT_DATA;
- } else if (Intent.ACTION_VIEW.equals(action)) {
- // Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
-
- // override action
- action = ACTION_DECRYPT_DATA;
- }
-
- /**
- * Main Actions
- */
- if (ACTION_DECRYPT_DATA.equals(action) && uri != null) {
- mFileFragmentBundle.putParcelable(DecryptFilesFragment.ARG_URI, uri);
-
- loadFragment(savedInstanceState, uri, false);
- } else if (ACTION_DECRYPT_DATA_OPEN.equals(action)) {
- loadFragment(savedInstanceState, null, true);
- } else if (ACTION_DECRYPT_DATA.equals(action)) {
- Log.e(Constants.TAG,
- "Include an Uri with setInputData() in your Intent!");
- }
- }
-
- private void loadFragment(Bundle savedInstanceState, Uri uri, boolean openDialog) {
- // However, if we're being restored from a previous state,
- // then we don't need to do anything and should return or else
- // we could end up with overlapping fragments.
- if (savedInstanceState != null) {
- return;
- }
-
- // Create an instance of the fragment
- mFragment = DecryptFilesFragment.newInstance(uri, openDialog);
-
- // Add the fragment to the 'fragment_container' FrameLayout
- // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.decrypt_files_fragment_container, mFragment)
- .commitAllowingStateLoss();
- // do it immediately!
- getSupportFragmentManager().executePendingTransactions();
- }
-
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java
deleted file mode 100644
index 234362edc..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package org.sufficientlysecure.keychain.ui;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckBox;
-import android.widget.TextView;
-
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
-import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
-import org.sufficientlysecure.keychain.ui.util.Notify;
-import org.sufficientlysecure.keychain.util.FileHelper;
-import org.sufficientlysecure.keychain.util.Log;
-
-import java.io.File;
-
-public class DecryptFilesFragment extends DecryptFragment {
- public static final String ARG_URI = "uri";
- public static final String ARG_OPEN_DIRECTLY = "open_directly";
-
- private static final int REQUEST_CODE_INPUT = 0x00007003;
- private static final int REQUEST_CODE_OUTPUT = 0x00007007;
-
- // view
- private TextView mFilename;
- private CheckBox mDeleteAfter;
- private View mDecryptButton;
-
- // model
- private Uri mInputUri = null;
- private Uri mOutputUri = null;
-
- private String mCurrentCryptoOperation;
-
- /**
- * Creates new instance of this fragment
- */
- public static DecryptFilesFragment newInstance(Uri uri, boolean openDirectly) {
- DecryptFilesFragment frag = new DecryptFilesFragment();
-
- Bundle args = new Bundle();
- args.putParcelable(ARG_URI, uri);
- args.putBoolean(ARG_OPEN_DIRECTLY, openDirectly);
-
- frag.setArguments(args);
-
- return frag;
- }
-
- /**
- * Inflate the layout for this fragment
- */
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.decrypt_files_fragment, container, false);
-
- mFilename = (TextView) view.findViewById(R.id.decrypt_files_filename);
- mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_files_delete_after_decryption);
- mDecryptButton = view.findViewById(R.id.decrypt_files_action_decrypt);
- view.findViewById(R.id.decrypt_files_browse).setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
- } else {
- FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*",
- REQUEST_CODE_INPUT);
- }
- }
- });
- mDecryptButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- decryptAction();
- }
- });
-
- return view;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- setInputUri(getArguments().<Uri>getParcelable(ARG_URI));
-
- if (getArguments().getBoolean(ARG_OPEN_DIRECTLY, false)) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
- } else {
- FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*",
- REQUEST_CODE_INPUT);
- }
- }
- }
-
- private void setInputUri(Uri inputUri) {
- if (inputUri == null) {
- mInputUri = null;
- mFilename.setText("");
- return;
- }
-
- mInputUri = inputUri;
- mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri));
- }
-
- private void decryptAction() {
- if (mInputUri == null) {
- Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show();
- return;
- }
-
- startDecryptFilenames();
- }
-
- 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);
- }
- return name;
- }
-
- private void askForOutputFilename(String originalFilename) {
- if (TextUtils.isEmpty(originalFilename)) {
- originalFilename = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri));
- }
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- File file = new File(mInputUri.getPath());
- File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
- File targetFile = new File(parentDir, originalFilename);
- FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file),
- getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT);
- } else {
- FileHelper.saveDocument(this, "*/*", originalFilename, REQUEST_CODE_OUTPUT);
- }
- }
-
- private void startDecrypt() {
- mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY;
- cryptoOperation(new CryptoInputParcel());
- }
-
- private void startDecryptFilenames() {
- mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_METADATA;
- cryptoOperation(new CryptoInputParcel());
- }
-
- @Override
- @SuppressLint("HandlerLeak")
- protected void cryptoOperation(CryptoInputParcel cryptoInput) {
- // Send all information needed to service to decrypt in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
-
- // fill values for this action
- Bundle data = new Bundle();
- // use current operation, either decrypt metadata or decrypt payload
- intent.setAction(mCurrentCryptoOperation);
-
- // data
- data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
-
- Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri);
-
- data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());
- data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri);
-
- data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());
- data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);
-
- data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
-
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Message is received after decrypting is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_decrypting),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- @Override
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- // handle pending messages
- if (handlePendingMessage(message)) {
- return;
- }
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
-
- DecryptVerifyResult pgpResult =
- returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
-
- if (pgpResult.success()) {
- switch (mCurrentCryptoOperation) {
- case KeychainIntentService.ACTION_DECRYPT_METADATA: {
- askForOutputFilename(pgpResult.getDecryptMetadata().getFilename());
- break;
- }
- case KeychainIntentService.ACTION_DECRYPT_VERIFY: {
- // display signature result in activity
- loadVerifyResult(pgpResult);
-
- if (mDeleteAfter.isChecked()) {
- // Create and show dialog to delete original file
- DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
- deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
- setInputUri(null);
- }
-
- /*
- // A future open after decryption feature
- if () {
- Intent viewFile = new Intent(Intent.ACTION_VIEW);
- viewFile.setInputData(mOutputUri);
- startActivity(viewFile);
- }
- */
- break;
- }
- default: {
- Log.e(Constants.TAG, "Bug: not supported operation!");
- break;
- }
- }
- }
- pgpResult.createNotify(getActivity()).show(DecryptFilesFragment.this);
- }
-
- }
- };
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- saveHandler.showProgressDialog(getActivity());
-
- // start service with intent
- getActivity().startService(intent);
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case REQUEST_CODE_INPUT: {
- if (resultCode == Activity.RESULT_OK && data != null) {
- setInputUri(data.getData());
- }
- return;
- }
-
- case REQUEST_CODE_OUTPUT: {
- // This happens after output file was selected, so start our operation
- if (resultCode == Activity.RESULT_OK && data != null) {
- mOutputUri = data.getData();
- startDecrypt();
- }
- return;
- }
-
- default: {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
- }
-
- @Override
- protected void onVerifyLoaded(boolean hideErrorOverlay) {
-
- }
-}
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 e9bc42a4d..37dd6afad 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
@@ -23,43 +23,43 @@ 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.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.View;
+import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import android.widget.ViewAnimator;
+import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
-import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
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.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
+import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.Preferences;
-public abstract class DecryptFragment extends CryptoOperationFragment implements
- LoaderManager.LoaderCallbacks<Cursor> {
+public abstract class DecryptFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
public static final int LOADER_ID_UNIFIED = 0;
+ public static final String ARG_DECRYPT_VERIFY_RESULT = "decrypt_verify_result";
protected LinearLayout mResultLayout;
protected ImageView mEncryptionIcon;
@@ -71,10 +71,11 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
protected TextView mSignatureEmail;
protected TextView mSignatureAction;
- private LinearLayout mContentLayout;
- private LinearLayout mErrorOverlayLayout;
-
private OpenPgpSignatureResult mSignatureResult;
+ private DecryptVerifyResult mDecryptVerifyResult;
+ private ViewAnimator mOverlayAnimator;
+
+ private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mImportOpHelper;
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
@@ -93,53 +94,55 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
mSignatureAction = (TextView) getActivity().findViewById(R.id.result_signature_action);
// Overlay
- mContentLayout = (LinearLayout) view.findViewById(R.id.decrypt_content);
- mErrorOverlayLayout = (LinearLayout) view.findViewById(R.id.decrypt_error_overlay);
+ mOverlayAnimator = (ViewAnimator) view;
Button vErrorOverlayButton = (Button) view.findViewById(R.id.decrypt_error_overlay_button);
- vErrorOverlayButton.setOnClickListener(new View.OnClickListener() {
+ vErrorOverlayButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ mOverlayAnimator.setDisplayedChild(0);
}
});
}
- private void lookupUnknownKey(long unknownKeyId) {
+ private void showErrorOverlay(boolean overlay) {
+ int child = overlay ? 1 : 0;
+ if (mOverlayAnimator.getDisplayedChild() != child) {
+ mOverlayAnimator.setDisplayedChild(child);
+ }
+ }
- // Message is received after importing is done in KeychainIntentService
- ServiceProgressHandler serviceHandler = new ServiceProgressHandler(getActivity()) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
+ outState.putParcelable(ARG_DECRYPT_VERIFY_RESULT, mDecryptVerifyResult);
+ }
- if (returnData == null) {
- return;
- }
+ @Override
+ public void onViewStateRestored(Bundle savedInstanceState) {
+ super.onViewStateRestored(savedInstanceState);
- final ImportKeyResult result =
- returnData.getParcelable(OperationResult.EXTRA_RESULT);
+ if (savedInstanceState == null) {
+ return;
+ }
- result.createNotify(getActivity()).show();
+ DecryptVerifyResult result = savedInstanceState.getParcelable(ARG_DECRYPT_VERIFY_RESULT);
+ if (result != null) {
+ loadVerifyResult(result);
+ }
+ }
- getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, DecryptFragment.this);
- }
- }
- };
+ private void lookupUnknownKey(long unknownKeyId) {
- // fill values for this action
- Bundle data = new Bundle();
+ final ArrayList<ParcelableKeyRing> keyList;
+ final String keyserver;
// search config
{
Preferences prefs = Preferences.getPreferences(getActivity());
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
- data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
+ keyserver = cloudPrefs.keyserver;
}
{
@@ -148,19 +151,43 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
selectedEntries.add(keyEntry);
- data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries);
+ keyList = selectedEntries;
}
- // Send all information needed to service to query keys in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+ CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> callback
+ = new CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult>() {
+
+ @Override
+ public ImportKeyringParcel createOperationInput() {
+ return new ImportKeyringParcel(keyList, keyserver);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(ImportKeyResult result) {
+ result.createNotify(getActivity()).show();
+
+ getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, DecryptFragment.this);
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+ // do nothing
+ }
+
+ @Override
+ public void onCryptoOperationError(ImportKeyResult result) {
+ result.createNotify(getActivity()).show();
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
+ };
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(serviceHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+ mImportOpHelper = new CryptoOperationHelper<>(1, this, callback, R.string.progress_importing);
- getActivity().startService(intent);
+ mImportOpHelper.cryptoOperation();
}
private void showKey(long keyId) {
@@ -178,43 +205,54 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
}
}
- /**
- * @return returns false if signature is invalid, key is revoked or expired.
- */
protected void loadVerifyResult(DecryptVerifyResult decryptVerifyResult) {
+ mDecryptVerifyResult = decryptVerifyResult;
mSignatureResult = decryptVerifyResult.getSignatureResult();
+ OpenPgpDecryptionResult decryptionResult = decryptVerifyResult.getDecryptionResult();
+
mResultLayout.setVisibility(View.VISIBLE);
- // unsigned data
- if (mSignatureResult == null) {
+ switch (decryptionResult.getResult()) {
+ case OpenPgpDecryptionResult.RESULT_ENCRYPTED: {
+ mEncryptionText.setText(R.string.decrypt_result_encrypted);
+ KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.ENCRYPTED);
+ break;
+ }
+
+ case OpenPgpDecryptionResult.RESULT_INSECURE: {
+ mEncryptionText.setText(R.string.decrypt_result_insecure);
+ KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.INSECURE);
+ break;
+ }
+
+ default:
+ case OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED: {
+ mEncryptionText.setText(R.string.decrypt_result_not_encrypted);
+ KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.NOT_ENCRYPTED);
+ break;
+ }
+ }
+
+ if (mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_NO_SIGNATURE) {
+ // no signature
setSignatureLayoutVisibility(View.GONE);
mSignatureText.setText(R.string.decrypt_result_no_signature);
KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.NOT_SIGNED);
- mEncryptionText.setText(R.string.decrypt_result_encrypted);
- KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.ENCRYPTED);
getLoaderManager().destroyLoader(LOADER_ID_UNIFIED);
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
-
- return;
- }
-
- if (mSignatureResult.isSignatureOnly()) {
- mEncryptionText.setText(R.string.decrypt_result_not_encrypted);
- KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.NOT_ENCRYPTED);
} else {
- mEncryptionText.setText(R.string.decrypt_result_encrypted);
- KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.ENCRYPTED);
- }
+ // signature present
- getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, this);
+ // after loader is restarted signature results are checked
+ getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, this);
+ }
}
private void setSignatureLayoutVisibility(int visibility) {
@@ -289,8 +327,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
// NOTE: Don't use revoked and expired fields from database, they don't show
// revoked/expired subkeys
- boolean isRevoked = mSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED;
- boolean isExpired = mSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED;
+ boolean isRevoked = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_REVOKED;
+ boolean isExpired = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED;
+ boolean isInsecure = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_INSECURE;
boolean isVerified = data.getInt(INDEX_VERIFIED) > 0;
boolean isYours = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
@@ -301,10 +340,7 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId);
- mErrorOverlayLayout.setVisibility(View.VISIBLE);
- mContentLayout.setVisibility(View.GONE);
-
- onVerifyLoaded(false);
+ onVerifyLoaded(true);
} else if (isExpired) {
mSignatureText.setText(R.string.decrypt_result_signature_expired_key);
@@ -313,21 +349,18 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId);
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
- } else if (isYours) {
-
- mSignatureText.setText(R.string.decrypt_result_signature_secret);
- KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED);
+ } else if (isInsecure) {
+ mSignatureText.setText(R.string.decrypt_result_insecure_cryptography);
+ KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.INSECURE);
setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId);
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
@@ -339,6 +372,8 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId);
+ showErrorOverlay(false);
+
onVerifyLoaded(true);
} else if (isVerified) {
@@ -348,8 +383,7 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId);
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
@@ -360,8 +394,7 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId);
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
}
@@ -382,9 +415,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
final long signatureKeyId = mSignatureResult.getKeyId();
- int result = mSignatureResult.getStatus();
- if (result != OpenPgpSignatureResult.SIGNATURE_KEY_MISSING
- && result != OpenPgpSignatureResult.SIGNATURE_ERROR) {
+ int result = mSignatureResult.getResult();
+ if (result != OpenPgpSignatureResult.RESULT_KEY_MISSING
+ && result != OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE) {
Log.e(Constants.TAG, "got missing status for non-missing key, shouldn't happen!");
}
@@ -402,9 +435,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
getActivity(), mSignatureResult.getKeyId()));
}
- switch (mSignatureResult.getStatus()) {
+ switch (mSignatureResult.getResult()) {
- case OpenPgpSignatureResult.SIGNATURE_KEY_MISSING: {
+ case OpenPgpSignatureResult.RESULT_KEY_MISSING: {
mSignatureText.setText(R.string.decrypt_result_signature_missing_key);
KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.UNKNOWN_KEY);
@@ -419,22 +452,20 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
}
});
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
break;
}
- case OpenPgpSignatureResult.SIGNATURE_ERROR: {
+ case OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE: {
mSignatureText.setText(R.string.decrypt_result_invalid_signature);
KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.INVALID);
setSignatureLayoutVisibility(View.GONE);
- mErrorOverlayLayout.setVisibility(View.VISIBLE);
- mContentLayout.setVisibility(View.GONE);
+ showErrorOverlay(true);
onVerifyLoaded(false);
break;
@@ -446,4 +477,11 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
protected abstract void onVerifyLoaded(boolean hideErrorOverlay);
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mImportOpHelper != null) {
+ mImportOpHelper.handleActivityResult(requestCode, resultCode, data);
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
new file mode 100644
index 000000000..26e56280a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
@@ -0,0 +1,945 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import android.app.Activity;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LabeledIntent;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.PopupMenu;
+import android.widget.PopupMenu.OnDismissListener;
+import android.widget.PopupMenu.OnMenuItemClickListener;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.ViewAnimator;
+
+import org.openintents.openpgp.OpenPgpMetadata;
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.sufficientlysecure.keychain.BuildConfig;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+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;
+// 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.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.util.FileHelper;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ParcelableHashMap;
+
+
+public class DecryptListFragment
+ 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;
+
+ private DecryptFilesAdapter mAdapter;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static DecryptListFragment newInstance(ArrayList<Uri> uris) {
+ DecryptListFragment frag = new DecryptListFragment();
+
+ Bundle args = new Bundle();
+ args.putParcelableArrayList(ARG_INPUT_URIS, uris);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ public DecryptListFragment() {
+ super(null);
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.decrypt_files_list_fragment, container, false);
+
+ RecyclerView vFilesList = (RecyclerView) view.findViewById(R.id.decrypted_files_list);
+
+ vFilesList.addItemDecoration(new SpacesItemDecoration(
+ FormattingUtils.dpToPx(getActivity(), 4)));
+ vFilesList.setHasFixedSize(true);
+ // TODO make this a grid, for tablets!
+ vFilesList.setLayoutManager(new LinearLayoutManager(getActivity()));
+ vFilesList.setItemAnimator(new DefaultItemAnimator());
+
+ mAdapter = new DecryptFilesAdapter(getActivity(), this);
+ vFilesList.setAdapter(mAdapter);
+
+ return view;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putParcelableArrayList(ARG_INPUT_URIS, mInputUris);
+
+ HashMap<Uri,DecryptVerifyResult> results = new HashMap<>(mInputUris.size());
+ for (Uri uri : mInputUris) {
+ if (mPendingInputUris.contains(uri)) {
+ continue;
+ }
+ DecryptVerifyResult result = mAdapter.getItemResult(uri);
+ if (result != null) {
+ results.put(uri, result);
+ }
+ }
+
+ 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);
+
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ 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, currentInputUri, cancelledUris,
+ outputUris != null ? outputUris.getMap() : null,
+ results != null ? results.getMap() : null
+ );
+ }
+
+ 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 (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 {
+ mOutputUris.put(uri, TemporaryStorageProvider.createFile(getActivity()));
+ mPendingInputUris.add(uri);
+ }
+ }
+
+ if (mCurrentInputUri == null) {
+ cryptoOperation();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_CODE_OUTPUT: {
+ // This happens after output file was selected, so start our operation
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ Uri decryptedFileUri = mOutputUris.get(mCurrentInputUri);
+ Uri saveUri = data.getData();
+ saveFile(decryptedFileUri, saveUri);
+ mCurrentInputUri = null;
+ }
+ return;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+ }
+
+ private void saveFile(Uri decryptedFileUri, Uri saveUri) {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ try {
+ FileHelper.copyUriData(activity, decryptedFileUri, saveUri);
+ Notify.create(activity, R.string.file_saved, Style.OK).show();
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "error saving file", e);
+ Notify.create(activity, R.string.error_saving_file, Style.ERROR).show();
+ }
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ mAdapter.setProgress(mCurrentInputUri, progress, max, msg);
+ return true;
+ }
+
+ @Override
+ public void onQueuedOperationError(DecryptVerifyResult result) {
+ final Uri uri = mCurrentInputUri;
+ mCurrentInputUri = null;
+
+ mAdapter.addResult(uri, result, null, null, null);
+
+ cryptoOperation();
+ }
+
+ @Override
+ public void onQueuedOperationSuccess(DecryptVerifyResult result) {
+ Uri uri = mCurrentInputUri;
+ mCurrentInputUri = null;
+
+ processResult(uri, result);
+
+ 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>() {
+ @Override
+ protected Drawable doInBackground(Void... params) {
+
+ Context context = getActivity();
+ if (result.getDecryptionMetadata() == null || context == null) {
+ return null;
+ }
+
+ String type = result.getDecryptionMetadata().getMimeType();
+ Uri outputUri = mOutputUris.get(uri);
+ if (type == null || outputUri == null) {
+ return null;
+ }
+
+ TemporaryStorageProvider.setMimeType(context, outputUri, type);
+
+ if (ClipDescription.compareMimeTypes(type, "image/*")) {
+ int px = FormattingUtils.dpToPx(context, 48);
+ Bitmap bitmap = FileHelper.getThumbnail(context, outputUri, new Point(px, px));
+ return new BitmapDrawable(context.getResources(), bitmap);
+ }
+
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(outputUri, type);
+
+ final List<ResolveInfo> matches =
+ context.getPackageManager().queryIntentActivities(intent, 0);
+ //noinspection LoopStatementThatDoesntLoop
+ for (ResolveInfo match : matches) {
+ return match.loadIcon(getActivity().getPackageManager());
+ }
+
+ return null;
+
+ }
+
+ @Override
+ protected void onPostExecute(Drawable icon) {
+ processResult(uri, result, icon);
+ }
+ }.execute();
+
+ }
+
+ private void processResult(final Uri uri, DecryptVerifyResult result, Drawable icon) {
+
+ OnClickListener onFileClick = null, onKeyClick = null;
+
+ OpenPgpSignatureResult sigResult = result.getSignatureResult();
+ if (sigResult != null) {
+ final long keyId = sigResult.getKeyId();
+ if (sigResult.getResult() != OpenPgpSignatureResult.RESULT_KEY_MISSING) {
+ onKeyClick = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+ Intent intent = new Intent(activity, ViewKeyActivity.class);
+ intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId));
+ activity.startActivity(intent);
+ }
+ };
+ }
+ }
+
+ if (result.success() && result.getDecryptionMetadata() != null) {
+ onFileClick = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ displayWithViewIntent(uri);
+ }
+ };
+ }
+
+ mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick);
+
+ }
+
+ 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) {
+ return;
+ }
+
+ final Uri outputUri = mOutputUris.get(uri);
+ final DecryptVerifyResult result = mAdapter.getItemResult(uri);
+ if (outputUri == null || result == null) {
+ return;
+ }
+
+ final OpenPgpMetadata metadata = result.getDecryptionMetadata();
+
+ // text/plain is a special case where we extract the uri content into
+ // the EXTRA_TEXT extra ourselves, and display a chooser which includes
+ // OpenKeychain's internal viewer
+ if ("text/plain".equals(metadata.getMimeType())) {
+
+ // this is a significant i/o operation, use an asynctask
+ new AsyncTask<Void,Void,Intent>() {
+
+ @Override
+ protected Intent doInBackground(Void... params) {
+
+ Activity activity = getActivity();
+ if (activity == null) {
+ return null;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(outputUri, "text/plain");
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ return intent;
+ }
+
+ @Override
+ protected void onPostExecute(Intent intent) {
+ // for result so we can possibly get a snackbar error from internal viewer
+ Activity activity = getActivity();
+ if (intent == null || activity == null) {
+ return;
+ }
+
+ LabeledIntent internalIntent = new LabeledIntent(
+ new Intent(intent)
+ .setClass(activity, DisplayTextActivity.class)
+ .putExtra(DisplayTextActivity.EXTRA_METADATA, result),
+ BuildConfig.APPLICATION_ID, R.string.view_internal, R.drawable.ic_launcher);
+
+ Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
+ chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
+ new Parcelable[] { internalIntent });
+
+ activity.startActivity(chooserIntent);
+ }
+
+ }.execute();
+
+ } else {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(outputUri, metadata.getMimeType());
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
+ chooserIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ activity.startActivity(chooserIntent);
+ }
+
+ }
+
+ @Override
+ public PgpDecryptVerifyInputParcel createOperationInput() {
+
+ if (mCurrentInputUri == null) {
+ if (mPendingInputUris.isEmpty()) {
+ // nothing left to do
+ return null;
+ }
+
+ mCurrentInputUri = mPendingInputUris.remove(0);
+ }
+
+ Uri currentOutputUri = mOutputUris.get(mCurrentInputUri);
+ Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + currentOutputUri);
+
+ return new PgpDecryptVerifyInputParcel(mCurrentInputUri, currentOutputUri)
+ .setAllowSymmetricDecryption(true);
+
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) {
+ return false;
+ }
+
+ // don't process menu items until all items are done!
+ if (!mPendingInputUris.isEmpty()) {
+ return true;
+ }
+
+ Activity activity = getActivity();
+ if (activity == null) {
+ return false;
+ }
+
+ ViewModel model = mAdapter.mMenuClickedModel;
+ DecryptVerifyResult result = model.mResult;
+ switch (menuItem.getItemId()) {
+ case R.id.view_log:
+ Intent intent = new Intent(activity, LogDisplayActivity.class);
+ intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result);
+ activity.startActivity(intent);
+ return true;
+ case R.id.decrypt_save:
+ OpenPgpMetadata metadata = result.getDecryptionMetadata();
+ if (metadata == null) {
+ return true;
+ }
+ mCurrentInputUri = model.mInputUri;
+ FileHelper.saveDocument(this, metadata.getFilename(), model.mInputUri, metadata.getMimeType(),
+ R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to, REQUEST_CODE_OUTPUT);
+ return true;
+ case R.id.decrypt_delete:
+ deleteFile(activity, model.mInputUri);
+ return true;
+ }
+ return false;
+ }
+
+ private void deleteFile(Activity activity, Uri uri) {
+
+ if ("file".equals(uri.getScheme())) {
+ File file = new File(uri.getPath());
+ if (file.delete()) {
+ Notify.create(activity, R.string.file_delete_ok, Style.OK).show();
+ } else {
+ Notify.create(activity, R.string.file_delete_none, Style.WARN).show();
+ }
+ return;
+ }
+
+ if ("content".equals(uri.getScheme())) {
+ try {
+ int deleted = activity.getContentResolver().delete(uri, null, null);
+ if (deleted > 0) {
+ Notify.create(activity, R.string.file_delete_ok, Style.OK).show();
+ } else {
+ Notify.create(activity, R.string.file_delete_none, Style.WARN).show();
+ }
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "exception deleting file", e);
+ Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show();
+ }
+ return;
+ }
+
+ Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show();
+
+ }
+
+ public static class DecryptFilesAdapter extends RecyclerView.Adapter<ViewHolder> {
+ private Context mContext;
+ private ArrayList<ViewModel> mDataset;
+ private OnMenuItemClickListener mMenuItemClickListener;
+ private ViewModel mMenuClickedModel;
+
+ public class ViewModel {
+ Context mContext;
+ Uri mInputUri;
+ DecryptVerifyResult mResult;
+ Drawable mIcon;
+
+ OnClickListener mOnFileClickListener;
+ OnClickListener mOnKeyClickListener;
+
+ 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) {
+ mResult = result;
+ }
+
+ void addIcon(Drawable icon) {
+ mIcon = icon;
+ }
+
+ void setOnClickListeners(OnClickListener onFileClick, OnClickListener onKeyClick) {
+ mOnFileClickListener = onFileClick;
+ mOnKeyClickListener = onKeyClick;
+ }
+
+ boolean hasResult() {
+ return mResult != null;
+ }
+
+ void setCancelled(OnClickListener retryListener) {
+ mCancelled = retryListener;
+ }
+
+ void setProgress(int progress, int max, String msg) {
+ if (msg != null) {
+ mProgressMsg = msg;
+ }
+ mProgress = progress;
+ mMax = max;
+ }
+
+ // Depends on inputUri only
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ViewModel viewModel = (ViewModel) o;
+ return !(mInputUri != null ? !mInputUri.equals(viewModel.mInputUri)
+ : viewModel.mInputUri != null);
+ }
+
+ // Depends on inputUri only
+ @Override
+ public int hashCode() {
+ return mResult != null ? mResult.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return mResult.toString();
+ }
+ }
+
+ // Provide a suitable constructor (depends on the kind of dataset)
+ public DecryptFilesAdapter(Context context, OnMenuItemClickListener menuItemClickListener) {
+ mContext = context;
+ mMenuItemClickListener = menuItemClickListener;
+ mDataset = new ArrayList<>();
+ }
+
+ // Create new views (invoked by the layout manager)
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ //inflate your layout and pass it to view holder
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.decrypt_list_entry, parent, false);
+ return new ViewHolder(v);
+ }
+
+ // Replace the contents of a view (invoked by the layout manager)
+ @Override
+ public void onBindViewHolder(ViewHolder holder, final int position) {
+ // - get element from your dataset at this position
+ // - 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;
+ }
+
+ if (model.mResult.success()) {
+ bindItemSuccess(holder, model);
+ } else {
+ bindItemFailure(holder, model);
+ }
+
+ }
+
+ 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);
+ }
+
+ holder.vProgress.setProgress(model.mProgress);
+ holder.vProgress.setMax(model.mMax);
+ if (model.mProgressMsg != null) {
+ holder.vProgressMsg.setText(model.mProgressMsg);
+ }
+ }
+
+ private void bindItemSuccess(ViewHolder holder, final ViewModel model) {
+ if (holder.vAnimator.getDisplayedChild() != 1) {
+ holder.vAnimator.setDisplayedChild(1);
+ }
+
+ KeyFormattingUtils.setStatus(mContext, holder, model.mResult);
+
+ final OpenPgpMetadata metadata = model.mResult.getDecryptionMetadata();
+
+ String filename;
+ if (metadata == null) {
+ filename = mContext.getString(R.string.filename_unknown);
+ } else if (TextUtils.isEmpty(metadata.getFilename())) {
+ filename = mContext.getString("text/plain".equals(metadata.getMimeType())
+ ? R.string.filename_unknown_text : R.string.filename_unknown);
+ } else {
+ filename = metadata.getFilename();
+ }
+ holder.vFilename.setText(filename);
+
+ long size = metadata == null ? 0 : metadata.getOriginalSize();
+ if (size == -1 || size == 0) {
+ holder.vFilesize.setText("");
+ } else {
+ holder.vFilesize.setText(FileHelper.readableFileSize(size));
+ }
+
+ if (model.mIcon != null) {
+ holder.vThumbnail.setImageDrawable(model.mIcon);
+ } else {
+ holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am);
+ }
+
+ holder.vFile.setOnClickListener(model.mOnFileClickListener);
+ holder.vSignatureLayout.setOnClickListener(model.mOnKeyClickListener);
+
+ holder.vContextMenu.setTag(model);
+ holder.vContextMenu.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mMenuClickedModel = model;
+ PopupMenu menu = new PopupMenu(mContext, view);
+ menu.inflate(R.menu.decrypt_item_context_menu);
+ menu.setOnMenuItemClickListener(mMenuItemClickListener);
+ menu.setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(PopupMenu popupMenu) {
+ mMenuClickedModel = null;
+ }
+ });
+ menu.show();
+ }
+ });
+ }
+
+ private void bindItemFailure(ViewHolder holder, final ViewModel model) {
+ if (holder.vAnimator.getDisplayedChild() != 2) {
+ holder.vAnimator.setDisplayedChild(2);
+ }
+
+ holder.vErrorMsg.setText(model.mResult.getLog().getLast().mType.getMsgId());
+
+ holder.vErrorViewLog.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(mContext, LogDisplayActivity.class);
+ intent.putExtra(LogDisplayFragment.EXTRA_RESULT, model.mResult);
+ mContext.startActivity(intent);
+ }
+ });
+
+ }
+
+ // Return the size of your dataset (invoked by the layout manager)
+ @Override
+ public int getItemCount() {
+ return mDataset.size();
+ }
+
+ public DecryptVerifyResult getItemResult(Uri uri) {
+ ViewModel model = new ViewModel(mContext, uri);
+ int pos = mDataset.indexOf(model);
+ if (pos == -1) {
+ return null;
+ }
+ model = mDataset.get(pos);
+
+ return model.mResult;
+ }
+
+ public void add(Uri uri) {
+ ViewModel newModel = new ViewModel(mContext, uri);
+ mDataset.add(newModel);
+ notifyItemInserted(mDataset.size());
+ }
+
+ public void setProgress(Uri uri, int progress, int max, String msg) {
+ ViewModel newModel = new ViewModel(mContext, uri);
+ int pos = mDataset.indexOf(newModel);
+ mDataset.get(pos).setProgress(progress, max, msg);
+ 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) {
+
+ ViewModel model = new ViewModel(mContext, uri);
+ int pos = mDataset.indexOf(model);
+ model = mDataset.get(pos);
+
+ model.addResult(result);
+ if (icon != null) {
+ model.addIcon(icon);
+ }
+ model.setOnClickListeners(onFileClick, onKeyClick);
+
+ notifyItemChanged(pos);
+ }
+
+ }
+
+
+ // Provide a reference to the views for each data item
+ // Complex data items may need more than one view per item, and
+ // you provide access to all the views for a data item in a view holder
+ public static class ViewHolder extends RecyclerView.ViewHolder implements StatusHolder {
+ public ViewAnimator vAnimator;
+
+ public ProgressBar vProgress;
+ public TextView vProgressMsg;
+
+ public View vFile;
+ public TextView vFilename;
+ public TextView vFilesize;
+ public ImageView vThumbnail;
+
+ public ImageView vEncStatusIcon;
+ public TextView vEncStatusText;
+
+ public ImageView vSigStatusIcon;
+ public TextView vSigStatusText;
+ public View vSignatureLayout;
+ public TextView vSignatureName;
+ public TextView vSignatureMail;
+ public TextView vSignatureAction;
+ public View vContextMenu;
+
+ public TextView vErrorMsg;
+ public ImageView vErrorViewLog;
+
+ public ImageView vCancelledRetry;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+
+ vAnimator = (ViewAnimator) itemView.findViewById(R.id.view_animator);
+
+ vProgress = (ProgressBar) itemView.findViewById(R.id.progress);
+ vProgressMsg = (TextView) itemView.findViewById(R.id.progress_msg);
+
+ vFile = itemView.findViewById(R.id.file);
+ vFilename = (TextView) itemView.findViewById(R.id.filename);
+ vFilesize = (TextView) itemView.findViewById(R.id.filesize);
+ vThumbnail = (ImageView) itemView.findViewById(R.id.thumbnail);
+
+ vEncStatusIcon = (ImageView) itemView.findViewById(R.id.result_encryption_icon);
+ vEncStatusText = (TextView) itemView.findViewById(R.id.result_encryption_text);
+
+ vSigStatusIcon = (ImageView) itemView.findViewById(R.id.result_signature_icon);
+ vSigStatusText = (TextView) itemView.findViewById(R.id.result_signature_text);
+ vSignatureLayout = itemView.findViewById(R.id.result_signature_layout);
+ vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name);
+ vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email);
+ vSignatureAction = (TextView) itemView.findViewById(R.id.result_signature_action);
+
+ vContextMenu = itemView.findViewById(R.id.context_menu);
+
+ 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
+ public ImageView getEncryptionStatusIcon() {
+ return vEncStatusIcon;
+ }
+
+ @Override
+ public TextView getEncryptionStatusText() {
+ return vEncStatusText;
+ }
+
+ @Override
+ public ImageView getSignatureStatusIcon() {
+ return vSigStatusIcon;
+ }
+
+ @Override
+ public TextView getSignatureStatusText() {
+ return vSigStatusText;
+ }
+
+ @Override
+ public View getSignatureLayout() {
+ return vSignatureLayout;
+ }
+
+ @Override
+ public TextView getSignatureAction() {
+ return vSignatureAction;
+ }
+
+ @Override
+ public TextView getSignatureUserName() {
+ return vSignatureName;
+ }
+
+ @Override
+ public TextView getSignatureUserEmail() {
+ return vSignatureMail;
+ }
+
+ @Override
+ public boolean hasEncrypt() {
+ return true;
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java
deleted file mode 100644
index e2eba3947..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
- * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package org.sufficientlysecure.keychain.ui;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.Toast;
-
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
-import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
-import org.sufficientlysecure.keychain.operations.results.OperationResult;
-import org.sufficientlysecure.keychain.operations.results.SingletonResult;
-import org.sufficientlysecure.keychain.pgp.PgpHelper;
-import org.sufficientlysecure.keychain.ui.base.BaseActivity;
-import org.sufficientlysecure.keychain.util.Log;
-
-import java.util.regex.Matcher;
-
-public class DecryptTextActivity extends BaseActivity {
-
- /* Intents */
- public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT;
- public static final String EXTRA_TEXT = OpenKeychainIntents.DECRYPT_EXTRA_TEXT;
-
- // intern
- public static final String ACTION_DECRYPT_FROM_CLIPBOARD = Constants.INTENT_PREFIX + "DECRYPT_TEXT_FROM_CLIPBOARD";
-
- DecryptTextFragment mFragment;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setFullScreenDialogClose(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- setResult(Activity.RESULT_CANCELED);
- finish();
- }
- }, false);
-
- // Handle intent actions
- handleActions(savedInstanceState, getIntent());
- }
-
- @Override
- protected void initLayout() {
- setContentView(R.layout.decrypt_text_activity);
- }
-
- /**
- * Fixing broken PGP MESSAGE Strings coming from GMail/AOSP Mail
- */
- private String fixPgpMessage(String message) {
- // windows newline -> unix newline
- message = message.replaceAll("\r\n", "\n");
- // Mac OS before X newline -> unix newline
- message = message.replaceAll("\r", "\n");
-
- // remove whitespaces before newline
- message = message.replaceAll(" +\n", "\n");
- // only two consecutive newlines are allowed
- message = message.replaceAll("\n\n+", "\n\n");
-
- // replace non breakable spaces
- message = message.replaceAll("\\xa0", " ");
-
- return message;
- }
-
- /**
- * Fixing broken PGP SIGNED MESSAGE Strings coming from GMail/AOSP Mail
- */
- private String fixPgpCleartextSignature(CharSequence input) {
- if (!TextUtils.isEmpty(input)) {
- String text = input.toString();
-
- // windows newline -> unix newline
- text = text.replaceAll("\r\n", "\n");
- // Mac OS before X newline -> unix newline
- text = text.replaceAll("\r", "\n");
-
- return text;
- } else {
- return null;
- }
- }
-
- private String getPgpContent(CharSequence input) {
- // only decrypt if clipboard content is available and a pgp message or cleartext signature
- if (!TextUtils.isEmpty(input)) {
- Log.dEscaped(Constants.TAG, "input: " + input);
-
- Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input);
- if (matcher.matches()) {
- String text = matcher.group(1);
- text = fixPgpMessage(text);
-
- Log.dEscaped(Constants.TAG, "input fixed: " + text);
- return text;
- } else {
- matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input);
- if (matcher.matches()) {
- String text = matcher.group(1);
- text = fixPgpCleartextSignature(text);
-
- Log.dEscaped(Constants.TAG, "input fixed: " + text);
- return text;
- } else {
- return null;
- }
- }
- } else {
- return null;
- }
- }
-
- /**
- * Handles all actions with this intent
- */
- private void handleActions(Bundle savedInstanceState, Intent intent) {
- String action = intent.getAction();
- Bundle extras = intent.getExtras();
- String type = intent.getType();
-
- if (extras == null) {
- extras = new Bundle();
- }
-
- if (Intent.ACTION_SEND.equals(action) && type != null) {
- Log.d(Constants.TAG, "ACTION_SEND");
- Log.logDebugBundle(extras, "SEND extras");
-
- // When sending to Keychain Decrypt via share menu
- if ("text/plain".equals(type)) {
- String sharedText = extras.getString(Intent.EXTRA_TEXT);
- sharedText = getPgpContent(sharedText);
-
- if (sharedText != null) {
- loadFragment(savedInstanceState, sharedText);
- } else {
- Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!");
- Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
- finish();
- }
- } else {
- Log.e(Constants.TAG, "ACTION_SEND received non-plaintext, this should not happen in this activity!");
- Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
- finish();
- }
- } else if (ACTION_DECRYPT_TEXT.equals(action)) {
- Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT");
-
- String extraText = extras.getString(EXTRA_TEXT);
- extraText = getPgpContent(extraText);
-
- if (extraText != null) {
- loadFragment(savedInstanceState, extraText);
- } else {
- Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!");
- Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
- finish();
- }
- } else if (ACTION_DECRYPT_FROM_CLIPBOARD.equals(action)) {
- Log.d(Constants.TAG, "ACTION_DECRYPT_FROM_CLIPBOARD");
-
- CharSequence clipboardText = ClipboardReflection.getClipboardText(this);
- String text = getPgpContent(clipboardText);
-
- if (text != null) {
- loadFragment(savedInstanceState, text);
- } else {
- returnInvalidResult();
- }
- } else if (ACTION_DECRYPT_TEXT.equals(action)) {
- Log.e(Constants.TAG, "Include the extra 'text' in your Intent!");
- Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
- finish();
- }
- }
-
- private void returnInvalidResult() {
- SingletonResult result = new SingletonResult(
- SingletonResult.RESULT_ERROR, OperationResult.LogType.MSG_NO_VALID_ENC);
- Intent intent = new Intent();
- intent.putExtra(SingletonResult.EXTRA_RESULT, result);
- setResult(RESULT_OK, intent);
- finish();
- }
-
- private void loadFragment(Bundle savedInstanceState, String ciphertext) {
- // However, if we're being restored from a previous state,
- // then we don't need to do anything and should return or else
- // we could end up with overlapping fragments.
- if (savedInstanceState != null) {
- return;
- }
-
- // Create an instance of the fragment
- mFragment = DecryptTextFragment.newInstance(ciphertext);
-
- // Add the fragment to the 'fragment_container' FrameLayout
- // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.decrypt_text_fragment_container, mFragment)
- .commitAllowingStateLoss();
- // do it immediately!
- getSupportFragmentManager().executePendingTransactions();
- }
-
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java
deleted file mode 100644
index 381da6f0d..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package org.sufficientlysecure.keychain.ui;
-
-import android.app.ProgressDialog;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.LinearLayout;
-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.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
-import org.sufficientlysecure.keychain.ui.util.Notify;
-import org.sufficientlysecure.keychain.util.ShareHelper;
-
-import java.io.UnsupportedEncodingException;
-
-public class DecryptTextFragment extends DecryptFragment {
- public static final String ARG_CIPHERTEXT = "ciphertext";
-
- // view
- private TextView mText;
-
- // model
- private String mCiphertext;
- private boolean mShowMenuOptions = false;
-
- /**
- * Creates new instance of this fragment
- */
- public static DecryptTextFragment newInstance(String ciphertext) {
- DecryptTextFragment frag = new DecryptTextFragment();
-
- Bundle args = new Bundle();
- args.putString(ARG_CIPHERTEXT, ciphertext);
-
- frag.setArguments(args);
-
- return frag;
- }
-
- /**
- * Inflate the layout for this fragment
- */
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false);
- mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext);
-
- return view;
- }
-
- /**
- * Create Intent Chooser but exclude decrypt activites
- */
- private Intent sendWithChooserExcludingDecrypt(String text) {
- Intent prototype = createSendIntent(text);
- String title = getString(R.string.title_share_message);
-
- // we don't want to decrypt the decrypted, no inception ;)
- String[] blacklist = new String[]{
- Constants.PACKAGE_NAME + ".ui.DecryptTextActivity",
- "org.thialfihar.android.apg.ui.DecryptActivity"
- };
-
- return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
- }
-
- private Intent createSendIntent(String text) {
- Intent sendIntent = new Intent(Intent.ACTION_SEND);
- sendIntent.putExtra(Intent.EXTRA_TEXT, text);
- sendIntent.setType("text/plain");
- return sendIntent;
- }
-
- private void copyToClipboard(String text) {
- ClipboardReflection.copyToClipboard(getActivity(), text);
- Notify.create(getActivity(), R.string.text_copied_to_clipboard, Notify.Style.OK).show();
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setHasOptionsMenu(true);
-
- String ciphertext = getArguments().getString(ARG_CIPHERTEXT);
- if (ciphertext != null) {
- mCiphertext = ciphertext;
- cryptoOperation(new CryptoInputParcel());
- }
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- if (mShowMenuOptions) {
- inflater.inflate(R.menu.decrypt_menu, menu);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.decrypt_share: {
- startActivity(sendWithChooserExcludingDecrypt(mText.getText().toString()));
- break;
- }
- case R.id.decrypt_copy: {
- copyToClipboard(mText.getText().toString());
- break;
- }
- default: {
- return super.onOptionsItemSelected(item);
- }
- }
-
- return true;
- }
-
- @Override
- protected void cryptoOperation(CryptoInputParcel cryptoInput) {
- // Send all information needed to service to decrypt in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
-
- // fill values for this action
- Bundle data = new Bundle();
-
- intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
-
- // data
- data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
- data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());
- data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes());
- data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
-
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Message is received after encrypting is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_decrypting),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- // handle pending messages
- if (handlePendingMessage(message)) {
- return;
- }
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
-
- DecryptVerifyResult pgpResult =
- returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
-
- if (pgpResult.success()) {
- byte[] decryptedMessage = returnData
- .getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
- String displayMessage;
- if (pgpResult.getCharset() != null) {
- try {
- displayMessage = new String(decryptedMessage, pgpResult.getCharset());
- } catch (UnsupportedEncodingException e) {
- // if we can't decode properly, just fall back to utf-8
- displayMessage = new String(decryptedMessage);
- }
- } else {
- displayMessage = new String(decryptedMessage);
- }
- mText.setText(displayMessage);
-
- // display signature result in activity
- loadVerifyResult(pgpResult);
- } else {
- // TODO: show also invalid layout with different text?
- }
- pgpResult.createNotify(getActivity()).show(DecryptTextFragment.this);
- }
- }
- };
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- saveHandler.showProgressDialog(getActivity());
-
- // start service with intent
- getActivity().startService(intent);
- }
-
- @Override
- protected void onVerifyLoaded(boolean hideErrorOverlay) {
- mShowMenuOptions = hideErrorOverlay;
- getActivity().supportInvalidateOptionsMenu();
- }
-}
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..478259133
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java
@@ -0,0 +1,426 @@
+/*
+ * 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.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
new file mode 100644
index 000000000..3c8e972b9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+
+import java.io.IOException;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.View;
+import android.widget.Toast;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+import org.sufficientlysecure.keychain.util.FileHelper;
+
+public class DisplayTextActivity extends BaseActivity {
+
+ public static final String EXTRA_METADATA = "metadata";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setFullScreenDialogClose(Activity.RESULT_CANCELED, false);
+
+ // Handle intent actions
+ handleActions(savedInstanceState, getIntent());
+ }
+
+ @Override
+ protected void initLayout() {
+ setContentView(R.layout.decrypt_text_activity);
+ }
+
+ /**
+ * Handles all actions with this intent
+ */
+ private void handleActions(Bundle savedInstanceState, Intent intent) {
+ if (savedInstanceState != null) {
+ return;
+ }
+
+ DecryptVerifyResult result = intent.getParcelableExtra(EXTRA_METADATA);
+
+ String plaintext;
+ try {
+ plaintext = FileHelper.readTextFromUri(this, intent.getData(), result.getCharset());
+ } catch (IOException e) {
+ Toast.makeText(this, R.string.error_preparing_data, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ if (plaintext != null) {
+ loadFragment(plaintext, result);
+ } else {
+ Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+
+ private void loadFragment(String plaintext, DecryptVerifyResult result) {
+ // Create an instance of the fragment
+ Fragment frag = DisplayTextFragment.newInstance(plaintext, result);
+
+ // Add the fragment to the 'fragment_container' FrameLayout
+ // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.decrypt_text_fragment_container, frag)
+ .commitAllowingStateLoss();
+ // do it immediately!
+ getSupportFragmentManager().executePendingTransactions();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java
new file mode 100644
index 000000000..dc06e9115
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+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 {
+
+ public static final String ARG_PLAINTEXT = "plaintext";
+
+ // view
+ private TextView mText;
+
+ // model (no state to persist though, that's all in arguments!)
+ private boolean mShowMenuOptions = false;
+
+ public static DisplayTextFragment newInstance(String plaintext, DecryptVerifyResult result) {
+ DisplayTextFragment frag = new DisplayTextFragment();
+
+ Bundle args = new Bundle();
+ args.putString(ARG_PLAINTEXT, plaintext);
+ args.putParcelable(ARG_DECRYPT_VERIFY_RESULT, result);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Create Intent Chooser but exclude decrypt activites
+ */
+ private Intent sendWithChooserExcludingDecrypt(String text) {
+ Intent prototype = createSendIntent(text);
+ String title = getString(R.string.title_share_message);
+
+ // we don't want to decrypt the decrypted, no inception ;)
+ String[] blacklist = new String[]{
+ Constants.PACKAGE_NAME + ".ui.DecryptActivity",
+ "org.thialfihar.android.apg.ui.DecryptActivity"
+ };
+
+ return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
+ }
+
+ private Intent createSendIntent(String text) {
+ Intent sendIntent = new Intent(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, text);
+ sendIntent.setType("text/plain");
+ return sendIntent;
+ }
+
+ private void copyToClipboard(String text) {
+ 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
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false);
+ mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext);
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ Bundle args = getArguments();
+
+ String plaintext = args.getString(ARG_PLAINTEXT);
+ DecryptVerifyResult result = args.getParcelable(ARG_DECRYPT_VERIFY_RESULT);
+
+ // display signature result in activity
+ mText.setText(plaintext);
+ loadVerifyResult(result);
+
+ }
+
+ @Override
+ protected void onVerifyLoaded(boolean hideErrorOverlay) {
+ mShowMenuOptions = hideErrorOverlay;
+ getActivity().supportInvalidateOptionsMenu();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ if (mShowMenuOptions) {
+ inflater.inflate(R.menu.decrypt_menu, menu);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.decrypt_share: {
+ startActivity(sendWithChooserExcludingDecrypt(mText.getText().toString()));
+ break;
+ }
+ case R.id.decrypt_copy: {
+ copyToClipboard(mText.getText().toString());
+ break;
+ }
+ default: {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ return true;
+ }
+
+}
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 897de8490..07b0a12d3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
@@ -18,7 +18,6 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
-import android.app.ProgressDialog;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
@@ -42,6 +41,7 @@ import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
@@ -49,8 +49,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
@@ -59,15 +57,21 @@ 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.dialog.*;
+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;
+import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
+import java.util.Date;
-public class EditKeyFragment extends CryptoOperationFragment implements
- LoaderManager.LoaderCallbacks<Cursor> {
+public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyringParcel, OperationResult>
+ implements LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel";
@@ -149,7 +153,7 @@ public class EditKeyFragment extends CryptoOperationFragment implements
if (mDataUri == null) {
returnKeyringParcel();
} else {
- cryptoOperation(new CryptoInputParcel());
+ cryptoOperation(new CryptoInputParcel(new Date()));
}
}
}, new OnClickListener() {
@@ -190,7 +194,7 @@ public class EditKeyFragment extends CryptoOperationFragment implements
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 {
@@ -415,15 +419,71 @@ public class EditKeyFragment extends CryptoOperationFragment implements
mSaveKeyringParcel.mRevokeSubKeys.add(keyId);
}
break;
- case EditSubkeyDialogFragment.MESSAGE_STRIP:
+ case EditSubkeyDialogFragment.MESSAGE_STRIP: {
+ SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
+ if (secretKeyType == SecretKeyType.GNU_DUMMY) {
+ // Key is already stripped; this is a no-op.
+ break;
+ }
+
SubkeyChange change = mSaveKeyringParcel.getSubkeyChange(keyId);
if (change == null) {
- mSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null));
+ mSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, false));
break;
}
// toggle
change.mDummyStrip = !change.mDummyStrip;
+ if (change.mDummyStrip && change.mMoveKeyToCard) {
+ // User had chosen to divert key, but now wants to strip it instead.
+ change.mMoveKeyToCard = false;
+ }
+ break;
+ }
+ 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();
}
@@ -521,7 +581,7 @@ public class EditKeyFragment extends CryptoOperationFragment implements
addSubkeyDialogFragment.show(getActivity().getSupportFragmentManager(), "addSubkeyDialog");
}
- private void returnKeyringParcel() {
+ protected void returnKeyringParcel() {
if (mSaveKeyringParcel.mAddUserIds.size() == 0) {
Notify.create(getActivity(), R.string.edit_key_error_add_identity, Notify.Style.ERROR).show();
return;
@@ -540,76 +600,6 @@ public class EditKeyFragment extends CryptoOperationFragment implements
getActivity().finish();
}
- @Override
- protected void cryptoOperation(CryptoInputParcel cryptoInput) {
-
- Log.d(Constants.TAG, "cryptoInput:\n" + cryptoInput);
- Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel);
-
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_saving),
- ProgressDialog.STYLE_HORIZONTAL,
- true,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (handlePendingMessage(message)) {
- return;
- }
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
-
- // get returned data bundle
- Bundle returnData = message.getData();
- if (returnData == null) {
- return;
- }
- final OperationResult result =
- returnData.getParcelable(OperationResult.EXTRA_RESULT);
- if (result == null) {
- return;
- }
-
- // if bad -> display here!
- if (!result.success()) {
- result.createNotify(getActivity()).show();
- return;
- }
-
- // 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();
-
- }
- }
- };
-
- // Send all information needed to service to import key in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_EDIT_KEYRING);
-
- // fill values for this action
- Bundle data = new Bundle();
- data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
- data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- saveHandler.showProgressDialog(getActivity());
-
- // start service with intent
- getActivity().startService(intent);
- }
-
/**
* Closes this activity, returning a result parcel with a single error log entry.
*/
@@ -624,4 +614,23 @@ public class EditKeyFragment extends CryptoOperationFragment implements
getActivity().finish();
}
+ @Override
+ public SaveKeyringParcel createOperationInput() {
+ return mSaveKeyringParcel;
+ }
+
+ @Override
+ 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);
+ activity.setResult(EditKeyActivity.RESULT_OK, intent);
+ activity.finish();
+
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java
new file mode 100644
index 000000000..4361705f9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.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.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+
+public class EncryptActivity extends BaseActivity {
+
+ // preselect ids, for internal use
+ public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
+ public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_IDS";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ Bundle extras = intent.getExtras();
+
+ if (extras == null) {
+ extras = new Bundle();
+ }
+
+ if (savedInstanceState == null) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+
+ // preselect keys given by intent
+ long signingKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
+ long[] encryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
+
+ Fragment modeFragment = EncryptModeAsymmetricFragment.newInstance(signingKeyId, encryptionKeyIds);
+ transaction.replace(R.id.encrypt_mode_container, modeFragment);
+ transaction.commit();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.check_use_symmetric: {
+ item.setChecked(!item.isChecked());
+ setModeFragment(item.isChecked());
+ return true;
+ }
+ default: {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.encrypt_activity, menu);
+
+ Fragment frag =
+ getSupportFragmentManager().findFragmentById(R.id.encrypt_mode_container);
+ boolean isSymmetric = frag instanceof EncryptModeSymmetricFragment;
+ menu.findItem(R.id.check_use_symmetric).setChecked(isSymmetric);
+
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ private void setModeFragment(boolean symmetric) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.encrypt_mode_container,
+ symmetric
+ ? EncryptModeSymmetricFragment.newInstance()
+ : EncryptModeAsymmetricFragment.newInstance(0, null)
+ );
+
+ // doesn't matter if the user doesn't look at the activity
+ transaction.commitAllowingStateLoss();
+ }
+
+ public EncryptModeFragment getModeFragment() {
+ return (EncryptModeFragment)
+ getSupportFragmentManager().findFragmentById(R.id.encrypt_mode_container);
+ }
+}
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 a6fad8881..84660ca7a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java
@@ -18,27 +18,36 @@
package org.sufficientlysecure.keychain.ui;
+
+import java.util.regex.Matcher;
+
+import android.app.Activity;
import android.content.Intent;
+import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
+import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
-import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
-
-import java.util.regex.Matcher;
+import org.sufficientlysecure.keychain.util.FileHelper;
public class EncryptDecryptOverviewFragment extends Fragment {
View mClipboardIcon;
+ private static final int REQUEST_CODE_INPUT = 0x00007003;
+
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@@ -74,31 +83,44 @@ public class EncryptDecryptOverviewFragment extends Fragment {
mDecryptFile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- Intent filesDecrypt = new Intent(getActivity(), DecryptFilesActivity.class);
- filesDecrypt.setAction(DecryptFilesActivity.ACTION_DECRYPT_DATA_OPEN);
- startActivity(filesDecrypt);
+ FileHelper.openDocument(EncryptDecryptOverviewFragment.this, null, "*/*", false, REQUEST_CODE_INPUT);
}
});
mDecryptFromClipboard.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- Intent clipboardDecrypt = new Intent(getActivity(), DecryptTextActivity.class);
- clipboardDecrypt.setAction(DecryptTextActivity.ACTION_DECRYPT_FROM_CLIPBOARD);
- startActivityForResult(clipboardDecrypt, 0);
+ decryptFromClipboard();
}
});
return view;
}
+ private void decryptFromClipboard() {
+
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ final CharSequence clipboardText = ClipboardReflection.getClipboardText(activity);
+ if (clipboardText == null || TextUtils.isEmpty(clipboardText)) {
+ Notify.create(activity, R.string.error_clipboard_empty, Style.ERROR).show();
+ return;
+ }
+
+ Intent clipboardDecrypt = new Intent(getActivity(), DecryptActivity.class);
+ clipboardDecrypt.setAction(DecryptActivity.ACTION_DECRYPT_FROM_CLIPBOARD);
+ startActivityForResult(clipboardDecrypt, 0);
+ }
+
@Override
public void onResume() {
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) {
@@ -135,12 +157,23 @@ public class EncryptDecryptOverviewFragment extends Fragment {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
- // 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);
+ if (requestCode != REQUEST_CODE_INPUT) {
+ return;
+ }
+
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ Uri uri = data.getData();
+ if (uri == null) {
+ Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show();
+ return;
+ }
+
+ Intent intent = new Intent(getActivity(), DecryptActivity.class);
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.setData(uri);
+ startActivity(intent);
+
}
}
+
}
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 b3ec60890..136787984 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java
@@ -22,76 +22,37 @@ import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
-import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
-import org.sufficientlysecure.keychain.ui.base.BaseActivity;
-import org.sufficientlysecure.keychain.util.Passphrase;
import java.util.ArrayList;
-public class EncryptFilesActivity extends BaseActivity implements
- EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric,
- EncryptFilesFragment.IMode {
+public class EncryptFilesActivity extends EncryptActivity {
- /* Intents */
+ // Intents
public static final String ACTION_ENCRYPT_DATA = OpenKeychainIntents.ENCRYPT_DATA;
// enables ASCII Armor for file encryption when uri is given
public static final String EXTRA_ASCII_ARMOR = OpenKeychainIntents.ENCRYPT_EXTRA_ASCII_ARMOR;
- // preselect ids, for internal use
- public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
- public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_ENCRYPTION_IDS";
-
- Fragment mModeFragment;
- EncryptFilesFragment mEncryptFragment;
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setFullScreenDialogClose(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- finish();
- }
- }, false);
-
- // Handle intent actions
- handleActions(getIntent(), savedInstanceState);
- }
+ setFullScreenDialogClose(Activity.RESULT_OK, false);
- @Override
- protected void initLayout() {
- setContentView(R.layout.encrypt_files_activity);
- }
-
- /**
- * Handles all actions with this intent
- */
- private void handleActions(Intent intent, Bundle savedInstanceState) {
+ Intent intent = getIntent();
String action = intent.getAction();
- Bundle extras = intent.getExtras();
String type = intent.getType();
ArrayList<Uri> uris = new ArrayList<>();
- if (extras == null) {
- extras = new Bundle();
- }
-
if (intent.getData() != null) {
uris.add(intent.getData());
}
- /*
- * Android's Action
- */
-
// When sending to OpenKeychain Encrypt via share menu
if (Intent.ACTION_SEND.equals(action) && type != null) {
// Files via content provider, override uri and action
@@ -103,56 +64,19 @@ public class EncryptFilesActivity extends BaseActivity implements
uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
}
- long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
- long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
- boolean useArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false);
-
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
- transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode");
-
- mEncryptFragment = EncryptFilesFragment.newInstance(uris, useArmor);
- transaction.replace(R.id.encrypt_file_container, mEncryptFragment, "files");
-
+ EncryptFilesFragment encryptFragment = EncryptFilesFragment.newInstance(uris);
+ transaction.replace(R.id.encrypt_file_container, encryptFragment);
transaction.commit();
-
- getSupportFragmentManager().executePendingTransactions();
}
- }
-
- @Override
- public void onModeChanged(boolean symmetric) {
- // switch fragments
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.encrypt_mode_container,
- symmetric
- ? EncryptModeSymmetricFragment.newInstance()
- : EncryptModeAsymmetricFragment.newInstance(0, null)
- )
- .commitAllowingStateLoss();
- getSupportFragmentManager().executePendingTransactions();
- }
-
- @Override
- public void onSignatureKeyIdChanged(long signatureKeyId) {
- mEncryptFragment.setSigningKeyId(signatureKeyId);
- }
- @Override
- public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) {
- mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds);
}
@Override
- public void onEncryptionUserIdsChanged(String[] encryptionUserIds) {
- mEncryptFragment.setEncryptionUserIds(encryptionUserIds);
- }
-
- @Override
- public void onPassphraseChanged(Passphrase passphrase) {
- mEncryptFragment.setPassphrase(passphrase);
+ protected void initLayout() {
+ setContentView(R.layout.encrypt_files_activity);
}
}
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 458810541..8572a5712 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
@@ -17,8 +17,17 @@
package org.sufficientlysecure.keychain.ui;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
import android.app.Activity;
-import android.app.ProgressDialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -26,8 +35,7 @@ import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
+import android.support.v4.app.FragmentActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -41,106 +49,74 @@ import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
-import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
-import org.sufficientlysecure.keychain.pgp.PgpConstants;
+import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
-import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
+import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
+import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.ShareHelper;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class EncryptFilesFragment extends CryptoOperationFragment {
-
- public interface IMode {
- public void onModeChanged(boolean symmetric);
- }
+public class EncryptFilesFragment
+ extends CachingCryptoOperationFragment<SignEncryptParcel, SignEncryptResult> {
+ public static final String ARG_DELETE_AFTER_ENCRYPT = "delete_after_encrypt";
+ public static final String ARG_ENCRYPT_FILENAMES = "encrypt_filenames";
+ public static final String ARG_USE_COMPRESSION = "use_compression";
public static final String ARG_USE_ASCII_ARMOR = "use_ascii_armor";
public static final String ARG_URIS = "uris";
- private static final int REQUEST_CODE_INPUT = 0x00007003;
+ public static final int REQUEST_CODE_INPUT = 0x00007003;
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
- private IMode mModeInterface;
-
- private boolean mSymmetricMode = false;
- private boolean mUseArmor = false;
- private boolean mUseCompression = true;
- private boolean mDeleteAfterEncrypt = false;
- private boolean mShareAfterEncrypt = false;
- private boolean mEncryptFilenames = true;
+ private boolean mUseArmor;
+ private boolean mUseCompression;
+ private boolean mDeleteAfterEncrypt;
+ private boolean mEncryptFilenames;
private boolean mHiddenRecipients = false;
- private long mEncryptionKeyIds[] = null;
- private String mEncryptionUserIds[] = null;
- private long mSigningKeyId = Constants.key.none;
- private Passphrase mPassphrase = new Passphrase();
+ private AfterEncryptAction mAfterEncryptAction;
+ private enum AfterEncryptAction {
+ SAVE, SHARE, COPY;
+ }
- private ArrayList<Uri> mOutputUris = new ArrayList<>();
+ private ArrayList<Uri> mOutputUris;
private RecyclerView mSelectedFiles;
- ArrayList<FilesAdapter.ViewModel> mFilesModels;
FilesAdapter mFilesAdapter;
/**
* Creates new instance of this fragment
*/
- public static EncryptFilesFragment newInstance(ArrayList<Uri> uris, boolean useArmor) {
+ public static EncryptFilesFragment newInstance(ArrayList<Uri> uris) {
EncryptFilesFragment frag = new EncryptFilesFragment();
Bundle args = new Bundle();
- args.putBoolean(ARG_USE_ASCII_ARMOR, useArmor);
args.putParcelableArrayList(ARG_URIS, uris);
frag.setArguments(args);
return frag;
}
- public void setEncryptionKeyIds(long[] encryptionKeyIds) {
- mEncryptionKeyIds = encryptionKeyIds;
- }
-
- public void setEncryptionUserIds(String[] encryptionUserIds) {
- mEncryptionUserIds = encryptionUserIds;
- }
-
- public void setSigningKeyId(long signingKeyId) {
- mSigningKeyId = signingKeyId;
- }
-
- public void setPassphrase(Passphrase passphrase) {
- mPassphrase = passphrase;
- }
-
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
- try {
- mModeInterface = (IMode) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity + " must be IMode");
+ if ( ! (activity instanceof EncryptActivity) ) {
+ throw new AssertionError(activity + " must inherit from EncryptionActivity");
}
}
@@ -158,38 +134,70 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
mSelectedFiles.setLayoutManager(new LinearLayoutManager(getActivity()));
mSelectedFiles.setItemAnimator(new DefaultItemAnimator());
- mFilesModels = new ArrayList<>();
- mFilesAdapter = new FilesAdapter(getActivity(), mFilesModels, new View.OnClickListener() {
+ mFilesAdapter = new FilesAdapter(getActivity(), new View.OnClickListener() {
@Override
public void onClick(View v) {
addInputUri();
}
});
- ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_URIS);
+ Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
+
+ ArrayList<Uri> inputUris = args.getParcelableArrayList(ARG_URIS);
if (inputUris != null) {
mFilesAdapter.addAll(inputUris);
}
- mUseArmor = getArguments().getBoolean(ARG_USE_ASCII_ARMOR);
mSelectedFiles.setAdapter(mFilesAdapter);
return view;
}
@Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putBoolean(ARG_DELETE_AFTER_ENCRYPT, mDeleteAfterEncrypt);
+ outState.putBoolean(ARG_USE_ASCII_ARMOR, mUseArmor);
+ outState.putBoolean(ARG_USE_COMPRESSION, mUseCompression);
+ outState.putBoolean(ARG_ENCRYPT_FILENAMES, mEncryptFilenames);
+
+ outState.putParcelableArrayList(ARG_URIS, mFilesAdapter.getAsArrayList());
+ }
+
+ @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ Preferences prefs = Preferences.getPreferences(getActivity());
+
+ Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
+ mDeleteAfterEncrypt = args.getBoolean(ARG_DELETE_AFTER_ENCRYPT, false);
+
+ if (args.containsKey(ARG_USE_ASCII_ARMOR)) {
+ mUseArmor = args.getBoolean(ARG_USE_ASCII_ARMOR, false);
+ } else {
+ mUseArmor = prefs.getUseArmor();
+ }
+
+ if (args.containsKey(ARG_USE_COMPRESSION)) {
+ mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
+ } else {
+ mUseCompression = prefs.getFilesUseCompression();
+ }
+
+ if (args.containsKey(ARG_ENCRYPT_FILENAMES)) {
+ mEncryptFilenames = args.getBoolean(ARG_ENCRYPT_FILENAMES, true);
+ } else {
+ mEncryptFilenames = prefs.getEncryptFilenames();
+ }
+
setHasOptionsMenu(true);
}
private void addInputUri() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT);
- } else {
- FileHelper.openFile(EncryptFilesFragment.this, mFilesModels.isEmpty() ?
- null : mFilesModels.get(mFilesModels.size() - 1).inputUri,
- "*/*", REQUEST_CODE_INPUT);
- }
+ FileHelper.openDocument(EncryptFilesFragment.this, mFilesAdapter.getModelCount() == 0 ?
+ null : mFilesAdapter.getModelItem(mFilesAdapter.getModelCount() - 1).inputUri,
+ "*/*", true, REQUEST_CODE_INPUT);
}
private void addInputUri(Uri inputUri) {
@@ -209,49 +217,16 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
}
private void showOutputFileDialog() {
- if (mFilesModels.size() > 1 || mFilesModels.isEmpty()) {
+ if (mFilesAdapter.getModelCount() != 1) {
throw new IllegalStateException();
}
- FilesAdapter.ViewModel model = mFilesModels.get(0);
+ FilesAdapter.ViewModel model = mFilesAdapter.getModelItem(0);
String targetName =
(mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri))
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- File file = new File(model.inputUri.getPath());
- File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
- File targetFile = new File(parentDir, targetName);
- FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file),
- getString(R.string.specify_file_to_encrypt_to), targetFile, REQUEST_CODE_OUTPUT);
- } else {
- FileHelper.saveDocument(this, "*/*", targetName, REQUEST_CODE_OUTPUT);
- }
- }
-
- private void encryptClicked(boolean share) {
- if (mFilesModels.isEmpty()) {
- Notify.create(getActivity(), R.string.error_no_file_selected,
- Notify.Style.ERROR).show(this);
- return;
- }
- if (share) {
- mOutputUris.clear();
- int filenameCounter = 1;
- for (FilesAdapter.ViewModel model : mFilesModels) {
- String targetName =
- (mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri))
- + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
- mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName));
- filenameCounter++;
- }
- startEncrypt(true);
- } else {
- if (mFilesModels.size() > 1) {
- Notify.create(getActivity(), R.string.error_multi_not_supported,
- Notify.Style.ERROR).show(this);
- return;
- }
- showOutputFileDialog();
- }
+ Uri inputUri = model.inputUri;
+ FileHelper.saveDocument(this, targetName, inputUri,
+ R.string.title_encrypt_to_file, R.string.specify_file_to_encrypt_to, REQUEST_CODE_OUTPUT);
}
public void addFile(Intent data) {
@@ -276,41 +251,49 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.encrypt_file_fragment, menu);
+
+ menu.findItem(R.id.check_delete_after_encrypt).setChecked(mDeleteAfterEncrypt);
+ menu.findItem(R.id.check_use_armor).setChecked(mUseArmor);
+ menu.findItem(R.id.check_enable_compression).setChecked(mUseCompression);
+ menu.findItem(R.id.check_encrypt_filenames).setChecked(mEncryptFilenames);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- if (item.isCheckable()) {
- item.setChecked(!item.isChecked());
- }
switch (item.getItemId()) {
case R.id.encrypt_save: {
- encryptClicked(false);
+ hideKeyboard();
+ mAfterEncryptAction = AfterEncryptAction.SAVE;
+ cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
case R.id.encrypt_share: {
- encryptClicked(true);
+ hideKeyboard();
+ mAfterEncryptAction = AfterEncryptAction.SHARE;
+ cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
- case R.id.check_use_symmetric: {
- mSymmetricMode = item.isChecked();
- mModeInterface.onModeChanged(mSymmetricMode);
+ case R.id.encrypt_copy: {
+ hideKeyboard();
+ mAfterEncryptAction = AfterEncryptAction.COPY;
+ cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
case R.id.check_use_armor: {
- mUseArmor = item.isChecked();
+ toggleUseArmor(item, !item.isChecked());
break;
}
case R.id.check_delete_after_encrypt: {
+ item.setChecked(!item.isChecked());
mDeleteAfterEncrypt = item.isChecked();
break;
}
case R.id.check_enable_compression: {
- mUseCompression = item.isChecked();
+ toggleEnableCompression(item, !item.isChecked());
break;
}
case R.id.check_encrypt_filenames: {
- mEncryptFilenames = item.isChecked();
+ toggleEncryptFilenamesCheck(item, !item.isChecked());
break;
}
// case R.id.check_hidden_recipients: {
@@ -325,111 +308,282 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
return true;
}
- protected boolean inputIsValid() {
- // file checks
+ public void toggleUseArmor(MenuItem item, final boolean useArmor) {
- if (mFilesModels.isEmpty()) {
- Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR)
- .show(this);
- return false;
- } else if (mFilesModels.size() > 1 && !mShareAfterEncrypt) {
- Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt");
- // This should be impossible...
- return false;
- } else if (mFilesModels.size() != mOutputUris.size()) {
- Log.e(Constants.TAG, "Aborting: mInputUris.size() != mOutputUris.size()");
- // This as well
- return false;
- }
+ mUseArmor = useArmor;
+ item.setChecked(useArmor);
- if (mSymmetricMode) {
- // symmetric encryption checks
+ Notify.create(getActivity(), useArmor
+ ? R.string.snack_armor_on
+ : R.string.snack_armor_off,
+ Notify.LENGTH_LONG, Style.OK, new ActionListener() {
+ @Override
+ public void onAction() {
+ Preferences.getPreferences(getActivity()).setUseArmor(useArmor);
+ Notify.create(getActivity(), useArmor
+ ? R.string.snack_armor_on
+ : R.string.snack_armor_off,
+ Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
+ .show(EncryptFilesFragment.this, false);
+ }
+ }, R.string.btn_save_default).show(this);
- if (mPassphrase == null) {
- Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
- .show(this);
- return false;
- }
- if (mPassphrase.isEmpty()) {
- Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
- .show(this);
- return false;
- }
+ }
- } else {
- // asymmetric encryption checks
+ public void toggleEnableCompression(MenuItem item, final boolean compress) {
- boolean gotEncryptionKeys = (mEncryptionKeyIds != null
- && mEncryptionKeyIds.length > 0);
+ mUseCompression = compress;
+ item.setChecked(compress);
- // Files must be encrypted, only text can be signed-only right now
- if (!gotEncryptionKeys) {
- Notify.create(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR)
- .show(this);
- return false;
+ Notify.create(getActivity(), compress
+ ? R.string.snack_compression_on
+ : R.string.snack_compression_off,
+ Notify.LENGTH_LONG, Style.OK, new ActionListener() {
+ @Override
+ public void onAction() {
+ Preferences.getPreferences(getActivity()).setFilesUseCompression(compress);
+ Notify.create(getActivity(), compress
+ ? R.string.snack_compression_on
+ : R.string.snack_compression_off,
+ Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
+ .show(EncryptFilesFragment.this, false);
+ }
+ }, R.string.btn_save_default).show(this);
+
+ }
+
+ public void toggleEncryptFilenamesCheck(MenuItem item, final boolean encryptFilenames) {
+
+ mEncryptFilenames = encryptFilenames;
+ item.setChecked(encryptFilenames);
+
+ Notify.create(getActivity(), encryptFilenames
+ ? R.string.snack_encrypt_filenames_on
+ : R.string.snack_encrypt_filenames_off,
+ Notify.LENGTH_LONG, Style.OK, new ActionListener() {
+ @Override
+ public void onAction() {
+ Preferences.getPreferences(getActivity()).setEncryptFilenames(encryptFilenames);
+ Notify.create(getActivity(), encryptFilenames
+ ? R.string.snack_encrypt_filenames_on
+ : R.string.snack_encrypt_filenames_off,
+ Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
+ .show(EncryptFilesFragment.this, false);
}
- }
- return true;
+ }, R.string.btn_save_default).show(this);
+
}
- public void onEncryptSuccess(final SignEncryptResult result) {
+ @Override
+ public void onQueuedOperationSuccess(final SignEncryptResult result) {
+ super.onQueuedOperationSuccess(result);
+
+ hideKeyboard();
+
+ // protected by Queueing*Fragment
+ FragmentActivity activity = getActivity();
+
if (mDeleteAfterEncrypt) {
+ // TODO make behavior coherent here
DeleteFileDialogFragment deleteFileDialog =
DeleteFileDialogFragment.newInstance(mFilesAdapter.getAsArrayList());
deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() {
@Override
public void onDeleted() {
- if (mShareAfterEncrypt) {
+ if (mAfterEncryptAction == AfterEncryptAction.SHARE) {
// 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 {
- if (mShareAfterEncrypt) {
- // Share encrypted message/file
- startActivity(sendWithChooserExcludingEncrypt());
- } else {
- // Save encrypted file
- result.createNotify(getActivity()).show();
+
+ switch (mAfterEncryptAction) {
+
+ case SHARE:
+ // Share encrypted message/file
+ startActivity(sendWithChooserExcludingEncrypt());
+ break;
+
+ case COPY:
+
+ 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(activity).show();
+ break;
+
+ case SAVE:
+ // Encrypted file was saved already, just show notification
+ result.createNotify(activity).show();
+ break;
}
}
+
+ }
+
+ // prepares mOutputUris, either directly and returns false, or indirectly
+ // which returns true and will call cryptoOperation after mOutputUris has
+ // been set at a later point.
+ private boolean prepareOutputStreams() {
+
+ switch (mAfterEncryptAction) {
+ default:
+ case SHARE:
+ mOutputUris = new ArrayList<>();
+ int filenameCounter = 1;
+ for (FilesAdapter.ViewModel model : mFilesAdapter.mDataset) {
+ String targetName = (mEncryptFilenames
+ ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri))
+ + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
+ mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName));
+ filenameCounter++;
+ }
+ return false;
+
+ case SAVE:
+ if (mFilesAdapter.getModelCount() > 1) {
+ Notify.create(getActivity(), R.string.error_multi_files, Notify.Style.ERROR).show(this);
+ return true;
+ }
+ showOutputFileDialog();
+ return true;
+
+ case COPY:
+ // nothing to do here, but make sure
+ if (mFilesAdapter.getModelCount() > 1) {
+ Notify.create(getActivity(), R.string.error_multi_clipboard, Notify.Style.ERROR).show(this);
+ return true;
+ }
+ mOutputUris = new ArrayList<>();
+ String targetName = (mEncryptFilenames
+ ? String.valueOf(1) : FileHelper.getFilename(getActivity(),
+ mFilesAdapter.getModelItem(0).inputUri)) + Constants.FILE_EXTENSION_ASC;
+ mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName, "text/plain"));
+ return false;
+ }
+
}
- protected SignEncryptParcel createEncryptBundle() {
+ public SignEncryptParcel createOperationInput() {
+
+ SignEncryptParcel actionsParcel = getCachedActionsParcel();
+
+ // we have three cases here: nothing cached, cached except output, fully cached
+ if (actionsParcel == null) {
+
+ // clear output uris for now, they will be created by prepareOutputStreams later
+ mOutputUris = null;
+
+ actionsParcel = createIncompleteCryptoInput();
+ // this is null if invalid, just return in that case
+ if (actionsParcel == null) {
+ return null;
+ }
+
+ cacheActionsParcel(actionsParcel);
+
+ }
+
+ // if it's incomplete, prepare output streams
+ if (actionsParcel.isIncomplete()) {
+ // if this is still null, prepare output streams again
+ if (mOutputUris == null) {
+ // this may interrupt the flow, and call us again from onActivityResult
+ if (prepareOutputStreams()) {
+ return null;
+ }
+ }
+
+ actionsParcel.addOutputUris(mOutputUris);
+ cacheActionsParcel(actionsParcel);
+
+ }
+
+ return actionsParcel;
+
+ }
+
+ protected SignEncryptParcel createIncompleteCryptoInput() {
+
+ if (mFilesAdapter.getModelCount() == 0) {
+ Notify.create(getActivity(), R.string.error_no_file_selected, Notify.Style.ERROR).show(this);
+ return null;
+ }
+
// fill values for this action
SignEncryptParcel data = new SignEncryptParcel();
data.addInputUris(mFilesAdapter.getAsArrayList());
- data.addOutputUris(mOutputUris);
if (mUseCompression) {
- data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
+ data.setCompressionAlgorithm(
+ PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT);
} else {
- data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
+ data.setCompressionAlgorithm(
+ PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED);
}
data.setHiddenRecipients(mHiddenRecipients);
- data.setEnableAsciiArmorOutput(mUseArmor);
- data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
- data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
+ data.setEnableAsciiArmorOutput(mAfterEncryptAction == AfterEncryptAction.COPY || mUseArmor);
+ data.setSymmetricEncryptionAlgorithm(
+ PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT);
+ data.setSignatureHashAlgorithm(
+ PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT);
+
+ EncryptActivity encryptActivity = (EncryptActivity) getActivity();
+ EncryptModeFragment modeFragment = encryptActivity.getModeFragment();
- if (mSymmetricMode) {
- Log.d(Constants.TAG, "Symmetric encryption enabled!");
- Passphrase passphrase = mPassphrase;
+ if (modeFragment.isAsymmetric()) {
+ long[] encryptionKeyIds = modeFragment.getAsymmetricEncryptionKeyIds();
+ long signingKeyId = modeFragment.getAsymmetricSigningKeyId();
+
+ boolean gotEncryptionKeys = (encryptionKeyIds != null && encryptionKeyIds.length > 0);
+
+ if (!gotEncryptionKeys && signingKeyId != 0) {
+ Notify.create(getActivity(), R.string.error_detached_signature, Notify.Style.ERROR).show(this);
+ return null;
+ }
+ if (!gotEncryptionKeys) {
+ Notify.create(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR).show(this);
+ return null;
+ }
+
+ data.setEncryptionMasterKeyIds(encryptionKeyIds);
+ data.setSignatureMasterKeyId(signingKeyId);
+ } else {
+ Passphrase passphrase = modeFragment.getSymmetricPassphrase();
+ if (passphrase == null) {
+ Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
+ .show(this);
+ return null;
+ }
if (passphrase.isEmpty()) {
- passphrase = null;
+ Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
+ .show(this);
+ return null;
}
data.setSymmetricPassphrase(passphrase);
- } else {
- data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
- data.setSignatureMasterKeyId(mSigningKeyId);
}
+
return data;
}
@@ -442,7 +596,7 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
// we don't want to encrypt the encrypted, no inception ;)
String[] blacklist = new String[]{
- Constants.PACKAGE_NAME + ".ui.EncryptFileActivity",
+ Constants.PACKAGE_NAME + ".ui.EncryptFilesActivity",
"org.thialfihar.android.apg.ui.EncryptActivity"
};
@@ -461,81 +615,28 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
}
sendIntent.setType(Constants.ENCRYPTED_FILES_MIME);
- if (!mSymmetricMode && mEncryptionUserIds != null) {
- Set<String> users = new HashSet<>();
- for (String user : mEncryptionUserIds) {
- KeyRing.UserId userId = KeyRing.splitUserId(user);
- if (userId.email != null) {
- users.add(userId.email);
- }
- }
- sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
+ EncryptActivity modeInterface = (EncryptActivity) getActivity();
+ EncryptModeFragment modeFragment = modeInterface.getModeFragment();
+ if (!modeFragment.isAsymmetric()) {
+ return sendIntent;
}
- return sendIntent;
- }
-
- public void startEncrypt(boolean share) {
- mShareAfterEncrypt = share;
- cryptoOperation();
- }
- @Override
- protected void cryptoOperation(CryptoInputParcel cryptoInput) {
-
- if (!inputIsValid()) {
- // Notify was created by inputIsValid.
- Log.d(Constants.TAG, "Input not valid!");
- return;
+ String[] encryptionUserIds = modeFragment.getAsymmetricEncryptionUserIds();
+ if (encryptionUserIds == null) {
+ return sendIntent;
}
- Log.d(Constants.TAG, "Input valid!");
-
- // Send all information needed to service to edit key in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
-
- final SignEncryptParcel input = createEncryptBundle();
-
- Bundle data = new Bundle();
- data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
- data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Message is received after encrypting is done in KeychainIntentService
- ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_encrypting),
- ProgressDialog.STYLE_HORIZONTAL,
- true,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- // handle pending messages
- if (handlePendingMessage(message)) {
- return;
- }
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- SignEncryptResult result =
- message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
- if (result.success()) {
- onEncryptSuccess(result);
- } else {
- result.createNotify(getActivity()).show();
- }
- }
+ Set<String> users = new HashSet<>();
+ for (String user : encryptionUserIds) {
+ KeyRing.UserId userId = KeyRing.splitUserId(user);
+ if (userId.email != null) {
+ users.add(userId.email);
}
- };
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(serviceHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- serviceHandler.showProgressDialog(getActivity());
+ }
+ // pass trough email addresses as extra for email applications
+ sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
- // start service with intent
- getActivity().startService(intent);
+ return sendIntent;
}
@Override
@@ -550,9 +651,11 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
case REQUEST_CODE_OUTPUT: {
// This happens after output file was selected, so start our operation
if (resultCode == Activity.RESULT_OK && data != null) {
- mOutputUris.clear();
+ mOutputUris = new ArrayList<>(1);
mOutputUris.add(data.getData());
- startEncrypt(false);
+ // make sure this is correct at this point
+ mAfterEncryptAction = AfterEncryptAction.SAVE;
+ cryptoOperation(new CryptoInputParcel(new Date()));
}
return;
}
@@ -640,9 +743,9 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
}
// Provide a suitable constructor (depends on the kind of dataset)
- public FilesAdapter(Activity activity, List<ViewModel> myDataset, View.OnClickListener onFooterClickListener) {
+ public FilesAdapter(Activity activity, View.OnClickListener onFooterClickListener) {
mActivity = activity;
- mDataset = myDataset;
+ mDataset = new ArrayList<>();
mFooterOnClickListener = onFooterClickListener;
}
@@ -696,7 +799,8 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
- return mDataset.size() + 1;
+ // one extra for the footer!
+ return mDataset.size() +1;
}
@Override
@@ -727,7 +831,7 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
for (Uri inputUri : inputUris) {
ViewModel newModel = new ViewModel(mActivity, inputUri);
if (mDataset.contains(newModel)) {
- Log.e(Constants.TAG, "Skipped duplicate " + inputUri.toString());
+ Log.e(Constants.TAG, "Skipped duplicate " + inputUri);
} else {
mDataset.add(newModel);
}
@@ -736,6 +840,14 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
}
}
+ public int getModelCount() {
+ return mDataset.size();
+ }
+
+ public ViewModel getModelItem(int position) {
+ return mDataset.get(position);
+ }
+
public void remove(ViewModel model) {
int position = mDataset.indexOf(model);
mDataset.remove(position);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java
index 6f56f2dc4..355c649e7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java
@@ -17,18 +17,15 @@
package org.sufficientlysecure.keychain.ui;
-import android.app.Activity;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ViewAnimator;
-import com.tokenautocomplete.TokenCompleteTextView;
-
+import com.tokenautocomplete.TokenCompleteTextView.TokenListener;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
@@ -38,39 +35,21 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
+import org.sufficientlysecure.keychain.ui.widget.KeySpinner.OnKeyChangedListener;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Passphrase;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
-public class EncryptModeAsymmetricFragment extends Fragment {
-
- public interface IAsymmetric {
-
- public void onSignatureKeyIdChanged(long signatureKeyId);
-
- public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds);
-
- public void onEncryptionUserIdsChanged(String[] encryptionUserIds);
- }
+public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
ProviderHelper mProviderHelper;
- // view
- private KeySpinner mSign;
+ private KeySpinner mSignKeySpinner;
private EncryptKeyCompletionView mEncryptKeyView;
- // model
- private IAsymmetric mEncryptInterface;
-
-// @Override
-// public void updateUi() {
-// if (mSign != null) {
-// mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
-// }
-// }
-
public static final String ARG_SINGATURE_KEY_ID = "signature_key_id";
public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
@@ -89,16 +68,6 @@ public class EncryptModeAsymmetricFragment extends Fragment {
return frag;
}
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- try {
- mEncryptInterface = (IAsymmetric) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity + " must implement IAsymmetric");
- }
- }
-
/**
* Inflate the layout for this fragment
*/
@@ -106,15 +75,38 @@ public class EncryptModeAsymmetricFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
- mSign = (KeySpinner) view.findViewById(R.id.sign);
- mSign.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
+ mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign);
+ mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
+ mEncryptKeyView.setThreshold(1); // Start working from first character
+
+ final ViewAnimator vSignatureIcon = (ViewAnimator) view.findViewById(R.id.result_signature_icon);
+ mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() {
@Override
public void onKeyChanged(long masterKeyId) {
- mEncryptInterface.onSignatureKeyIdChanged(masterKeyId);
+ int child = masterKeyId != Constants.key.none ? 1 : 0;
+ if (vSignatureIcon.getDisplayedChild() != child) {
+ vSignatureIcon.setDisplayedChild(child);
+ }
+ }
+ });
+
+ final ViewAnimator vEncryptionIcon = (ViewAnimator) view.findViewById(R.id.result_encryption_icon);
+ mEncryptKeyView.setTokenListener(new TokenListener() {
+ @Override
+ public void onTokenAdded(Object o) {
+ if (vEncryptionIcon.getDisplayedChild() != 1) {
+ vEncryptionIcon.setDisplayedChild(1);
+ }
+ }
+
+ @Override
+ public void onTokenRemoved(Object o) {
+ int child = mEncryptKeyView.getObjects().isEmpty() ? 0 : 1;
+ if (vEncryptionIcon.getDisplayedChild() != child) {
+ vEncryptionIcon.setDisplayedChild(child);
+ }
}
});
- mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
- mEncryptKeyView.setThreshold(1); // Start working from first character
return view;
}
@@ -124,39 +116,28 @@ public class EncryptModeAsymmetricFragment extends Fragment {
super.onActivityCreated(savedInstanceState);
mProviderHelper = new ProviderHelper(getActivity());
- // preselect keys given
- long signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID);
- long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
- preselectKeys(signatureKeyId, encryptionKeyIds);
-
- mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() {
- @Override
- public void onTokenAdded(Object token) {
- if (token instanceof KeyItem) {
- updateEncryptionKeys();
- }
+ // preselect keys given, from state or arguments
+ if (savedInstanceState == null) {
+ Long signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID);
+ if (signatureKeyId == Constants.key.none) {
+ signatureKeyId = null;
}
+ long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
+ preselectKeys(signatureKeyId, encryptionKeyIds);
+ }
- @Override
- public void onTokenRemoved(Object token) {
- if (token instanceof KeyItem) {
- updateEncryptionKeys();
- }
- }
- });
}
/**
* If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those!
*/
- private void preselectKeys(long signatureKeyId, long[] encryptionKeyIds) {
- if (signatureKeyId != Constants.key.none) {
+ private void preselectKeys(Long signatureKeyId, long[] encryptionKeyIds) {
+ if (signatureKeyId != null) {
try {
CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing(
KeyRings.buildUnifiedKeyRingUri(signatureKeyId));
if (keyring.hasAnySecret()) {
- mEncryptInterface.onSignatureKeyIdChanged(keyring.getMasterKeyId());
- mSign.setSelectedKeyId(signatureKeyId);
+ mSignKeySpinner.setPreSelectedKeyId(signatureKeyId);
}
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
@@ -175,27 +156,55 @@ public class EncryptModeAsymmetricFragment extends Fragment {
}
// This is to work-around a rendering bug in TokenCompleteTextView
mEncryptKeyView.requestFocus();
- updateEncryptionKeys();
}
}
- private void updateEncryptionKeys() {
- List<Object> objects = mEncryptKeyView.getObjects();
+ @Override
+ public boolean isAsymmetric() {
+ return true;
+ }
+
+ @Override
+ public long getAsymmetricSigningKeyId() {
+ return mSignKeySpinner.getSelectedKeyId();
+ }
+
+ @Override
+ public long[] getAsymmetricEncryptionKeyIds() {
List<Long> keyIds = new ArrayList<>();
- List<String> userIds = new ArrayList<>();
- for (Object object : objects) {
+ for (Object object : mEncryptKeyView.getObjects()) {
if (object instanceof KeyItem) {
keyIds.add(((KeyItem) object).mKeyId);
- userIds.add(((KeyItem) object).mUserIdFull);
}
}
+
long[] keyIdsArr = new long[keyIds.size()];
Iterator<Long> iterator = keyIds.iterator();
for (int i = 0; i < keyIds.size(); i++) {
keyIdsArr[i] = iterator.next();
}
- mEncryptInterface.onEncryptionKeyIdsChanged(keyIdsArr);
- mEncryptInterface.onEncryptionUserIdsChanged(userIds.toArray(new String[userIds.size()]));
+
+ return keyIdsArr;
+ }
+
+ @Override
+ public String[] getAsymmetricEncryptionUserIds() {
+
+ List<String> userIds = new ArrayList<>();
+ for (Object object : mEncryptKeyView.getObjects()) {
+ if (object instanceof KeyItem) {
+ userIds.add(((KeyItem) object).mUserIdFull);
+ }
+ }
+
+ return userIds.toArray(new String[userIds.size()]);
+
}
+
+ @Override
+ public Passphrase getSymmetricPassphrase() {
+ throw new UnsupportedOperationException("should never happen, this is a programming error!");
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeFragment.java
new file mode 100644
index 000000000..0b9672654
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeFragment.java
@@ -0,0 +1,19 @@
+package org.sufficientlysecure.keychain.ui;
+
+
+import android.support.v4.app.Fragment;
+
+import org.sufficientlysecure.keychain.util.Passphrase;
+
+
+public abstract class EncryptModeFragment extends Fragment {
+
+ public abstract boolean isAsymmetric();
+
+ public abstract long getAsymmetricSigningKeyId();
+ public abstract long[] getAsymmetricEncryptionKeyIds();
+ public abstract String[] getAsymmetricEncryptionUserIds();
+
+ public abstract Passphrase getSymmetricPassphrase();
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeSymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeSymmetricFragment.java
index 48b1f4983..b92a73731 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeSymmetricFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeSymmetricFragment.java
@@ -17,11 +17,7 @@
package org.sufficientlysecure.keychain.ui;
-import android.app.Activity;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.text.Editable;
-import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -30,14 +26,7 @@ import android.widget.EditText;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Passphrase;
-public class EncryptModeSymmetricFragment extends Fragment {
-
- public interface ISymmetric {
-
- public void onPassphraseChanged(Passphrase passphrase);
- }
-
- private ISymmetric mEncryptInterface;
+public class EncryptModeSymmetricFragment extends EncryptModeFragment {
private EditText mPassphrase;
private EditText mPassphraseAgain;
@@ -55,52 +44,53 @@ public class EncryptModeSymmetricFragment extends Fragment {
}
@Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- try {
- mEncryptInterface = (ISymmetric) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString() + " must implement ISymmetric");
- }
- }
-
- /**
- * Inflate the layout for this fragment
- */
- @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_symmetric_fragment, container, false);
mPassphrase = (EditText) view.findViewById(R.id.passphrase);
mPassphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain);
- 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) {
- }
+ return view;
+ }
+
+ @Override
+ public boolean isAsymmetric() {
+ return false;
+ }
- @Override
- public void afterTextChanged(Editable s) {
- // update passphrase in EncryptActivity
- Passphrase p1 = new Passphrase(mPassphrase.getText());
- Passphrase p2 = new Passphrase(mPassphraseAgain.getText());
- boolean passesEquals = (p1.equals(p2));
+ @Override
+ public long getAsymmetricSigningKeyId() {
+ throw new UnsupportedOperationException("should never happen, this is a programming error!");
+ }
+
+ @Override
+ public long[] getAsymmetricEncryptionKeyIds() {
+ throw new UnsupportedOperationException("should never happen, this is a programming error!");
+ }
+
+ @Override
+ public String[] getAsymmetricEncryptionUserIds() {
+ throw new UnsupportedOperationException("should never happen, this is a programming error!");
+ }
+
+ @Override
+ public Passphrase getSymmetricPassphrase() {
+ Passphrase p1 = null, p2 = null;
+ try {
+ p1 = new Passphrase(mPassphrase.getText());
+ p2 = new Passphrase(mPassphraseAgain.getText());
+ if (!p1.equals(p2)) {
+ return null;
+ }
+ return new Passphrase(mPassphrase.getText());
+ } finally {
+ if (p1 != null) {
p1.removeFromMemory();
+ }
+ if (p2 != null) {
p2.removeFromMemory();
- if (passesEquals) {
- mEncryptInterface.onPassphraseChanged(new Passphrase(mPassphrase.getText()));
- } else {
- mEncryptInterface.onPassphraseChanged(null);
- }
}
- };
- mPassphrase.addTextChangedListener(textWatcher);
- mPassphraseAgain.addTextChangedListener(textWatcher);
-
- return view;
+ }
}
}
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 52d098adc..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,22 +18,17 @@
package org.sufficientlysecure.keychain.ui;
+import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
-import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
-import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log;
-import org.sufficientlysecure.keychain.util.Passphrase;
-public class EncryptTextActivity extends BaseActivity implements
- EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric,
- EncryptTextFragment.IMode {
+public class EncryptTextActivity extends EncryptActivity {
/* Intents */
public static final String ACTION_ENCRYPT_TEXT = OpenKeychainIntents.ENCRYPT_TEXT;
@@ -41,40 +36,13 @@ public class EncryptTextActivity extends BaseActivity implements
/* EXTRA keys for input */
public static final String EXTRA_TEXT = OpenKeychainIntents.ENCRYPT_EXTRA_TEXT;
- // preselect ids, for internal use
- public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
- public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_IDS";
-
- Fragment mModeFragment;
- EncryptTextFragment mEncryptFragment;
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setFullScreenDialogClose(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- finish();
- }
- }, false);
-
- // Handle intent actions
- handleActions(getIntent(), savedInstanceState);
- }
-
- @Override
- protected void initLayout() {
- setContentView(R.layout.encrypt_text_activity);
- }
-
+ setFullScreenDialogClose(Activity.RESULT_OK, false);
- /**
- * Handles all actions with this intent
- *
- * @param intent
- */
- private void handleActions(Intent intent, Bundle savedInstanceState) {
+ Intent intent = getIntent();
String action = intent.getAction();
Bundle extras = intent.getExtras();
String type = intent.getType();
@@ -83,10 +51,6 @@ public class EncryptTextActivity extends BaseActivity implements
extras = new Bundle();
}
- /*
- * Android's Action
- */
-
// When sending to OpenKeychain Encrypt via share menu
if (Intent.ACTION_SEND.equals(action) && type != null) {
Log.logDebugBundle(extras, "extras");
@@ -108,55 +72,19 @@ public class EncryptTextActivity extends BaseActivity implements
textData = "";
}
- // preselect keys given by intent
- long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
- long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
-
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
- transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode");
-
- mEncryptFragment = EncryptTextFragment.newInstance(textData);
- transaction.replace(R.id.encrypt_text_container, mEncryptFragment, "text");
-
+ EncryptTextFragment encryptFragment = EncryptTextFragment.newInstance(textData);
+ transaction.replace(R.id.encrypt_text_container, encryptFragment);
transaction.commit();
-
- getSupportFragmentManager().executePendingTransactions();
}
- }
-
- @Override
- public void onModeChanged(boolean symmetric) {
- // switch fragments
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.encrypt_mode_container,
- symmetric
- ? EncryptModeSymmetricFragment.newInstance()
- : EncryptModeAsymmetricFragment.newInstance(0, null)
- )
- .commitAllowingStateLoss();
- getSupportFragmentManager().executePendingTransactions();
- }
-
- @Override
- public void onSignatureKeyIdChanged(long signatureKeyId) {
- mEncryptFragment.setSigningKeyId(signatureKeyId);
- }
- @Override
- public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) {
- mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds);
}
@Override
- public void onEncryptionUserIdsChanged(String[] encryptionUserIds) {
- mEncryptFragment.setEncryptionUserIds(encryptionUserIds);
+ protected void initLayout() {
+ setContentView(R.layout.encrypt_text_activity);
}
- @Override
- public void onPassphraseChanged(Passphrase passphrase) {
- mEncryptFragment.setSymmetricPassphrase(passphrase);
- }
}
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 3f9147cc4..ab676285e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
@@ -18,11 +18,11 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
-import android.app.ProgressDialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
@@ -33,67 +33,37 @@ import android.view.View;
import android.view.ViewGroup;
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.PgpSecurityConstants;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
-import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
-import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
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;
-public class EncryptTextFragment extends CryptoOperationFragment {
-
- public interface IMode {
- public void onModeChanged(boolean symmetric);
- }
+public class EncryptTextFragment
+ extends CachingCryptoOperationFragment<SignEncryptParcel, SignEncryptResult> {
public static final String ARG_TEXT = "text";
+ public static final String ARG_USE_COMPRESSION = "use_compression";
- private IMode mModeInterface;
-
- private boolean mSymmetricMode = false;
- private boolean mShareAfterEncrypt = false;
- private boolean mUseCompression = true;
+ private boolean mShareAfterEncrypt;
+ private boolean mUseCompression;
private boolean mHiddenRecipients = false;
- private long mEncryptionKeyIds[] = null;
- private String mEncryptionUserIds[] = null;
- // TODO Constants.key.none? What's wrong with a null value?
- private long mSigningKeyId = Constants.key.none;
- private Passphrase mSymmetricPassphrase = new Passphrase();
private String mMessage = "";
- private TextView mText;
-
- public void setEncryptionKeyIds(long[] encryptionKeyIds) {
- mEncryptionKeyIds = encryptionKeyIds;
- }
-
- public void setEncryptionUserIds(String[] encryptionUserIds) {
- mEncryptionUserIds = encryptionUserIds;
- }
-
- public void setSigningKeyId(long signingKeyId) {
- mSigningKeyId = signingKeyId;
- }
-
- public void setSymmetricPassphrase(Passphrase passphrase) {
- mSymmetricPassphrase = passphrase;
- }
-
/**
* Creates new instance of this fragment
*/
@@ -110,10 +80,8 @@ public class EncryptTextFragment extends CryptoOperationFragment {
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
- try {
- mModeInterface = (IMode) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString() + " must implement IMode");
+ if ( ! (activity instanceof EncryptActivity) ) {
+ throw new AssertionError(activity + " must inherit from EncryptionActivity");
}
}
@@ -124,8 +92,8 @@ public class EncryptTextFragment extends CryptoOperationFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_text_fragment, container, false);
- mText = (TextView) view.findViewById(R.id.encrypt_text_text);
- mText.addTextChangedListener(new TextWatcher() {
+ TextView textView = (TextView) view.findViewById(R.id.encrypt_text_text);
+ textView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -144,39 +112,53 @@ public class EncryptTextFragment extends CryptoOperationFragment {
// set initial text
if (mMessage != null) {
- mText.setText(mMessage);
+ textView.setText(mMessage);
}
return view;
}
@Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(ARG_USE_COMPRESSION, mUseCompression);
+ }
+
+ @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mMessage = getArguments().getString(ARG_TEXT);
+ if (savedInstanceState == null) {
+ mMessage = getArguments().getString(ARG_TEXT);
+ }
+
+ Preferences prefs = Preferences.getPreferences(getActivity());
+
+ Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
+
+ mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
+ if (args.containsKey(ARG_USE_COMPRESSION)) {
+ mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
+ } else {
+ mUseCompression = prefs.getTextUseCompression();
+ }
setHasOptionsMenu(true);
+
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.encrypt_text_fragment, menu);
+
+ menu.findItem(R.id.check_enable_compression).setChecked(mUseCompression);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- if (item.isCheckable()) {
- item.setChecked(!item.isChecked());
- }
switch (item.getItemId()) {
- case R.id.check_use_symmetric: {
- mSymmetricMode = item.isChecked();
- mModeInterface.onModeChanged(mSymmetricMode);
- break;
- }
case R.id.check_enable_compression: {
- mUseCompression = item.isChecked();
+ toggleEnableCompression(item, !item.isChecked());
break;
}
// case R.id.check_hidden_recipients: {
@@ -185,11 +167,15 @@ public class EncryptTextFragment extends CryptoOperationFragment {
// break;
// }
case R.id.encrypt_copy: {
- startEncrypt(false);
+ hideKeyboard();
+ mShareAfterEncrypt = false;
+ cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
case R.id.encrypt_share: {
- startEncrypt(true);
+ hideKeyboard();
+ mShareAfterEncrypt = true;
+ cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
default: {
@@ -199,22 +185,36 @@ public class EncryptTextFragment extends CryptoOperationFragment {
return true;
}
+ public void toggleEnableCompression(MenuItem item, final boolean compress) {
+
+ mUseCompression = compress;
+ item.setChecked(compress);
+
+ Notify.create(getActivity(), compress
+ ? R.string.snack_compression_on
+ : R.string.snack_compression_off,
+ Notify.LENGTH_LONG, Style.OK, new ActionListener() {
+ @Override
+ public void onAction() {
+ Preferences.getPreferences(getActivity()).setTextUseCompression(compress);
+ Notify.create(getActivity(), compress
+ ? R.string.snack_compression_on
+ : R.string.snack_compression_off,
+ Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
+ .show(EncryptTextFragment.this, false);
+ }
+ }, R.string.btn_save_default).show(this);
- protected void onEncryptSuccess(SignEncryptResult result) {
- 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));
- }
}
- protected SignEncryptParcel createEncryptBundle() {
+ public SignEncryptParcel createOperationInput() {
+
+ if (mMessage == null || mMessage.isEmpty()) {
+ Notify.create(getActivity(), R.string.error_empty_text, Notify.Style.ERROR)
+ .show(this);
+ return null;
+ }
+
// fill values for this action
SignEncryptParcel data = new SignEncryptParcel();
@@ -222,33 +222,71 @@ public class EncryptTextFragment extends CryptoOperationFragment {
data.setCleartextSignature(true);
if (mUseCompression) {
- data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
+ data.setCompressionAlgorithm(
+ PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT);
} else {
- data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
+ data.setCompressionAlgorithm(
+ PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED);
}
data.setHiddenRecipients(mHiddenRecipients);
- data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
- data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
+ data.setSymmetricEncryptionAlgorithm(
+ PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT);
+ data.setSignatureHashAlgorithm(
+ PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT);
// Always use armor for messages
data.setEnableAsciiArmorOutput(true);
- if (mSymmetricMode) {
- Log.d(Constants.TAG, "Symmetric encryption enabled!");
- Passphrase passphrase = mSymmetricPassphrase;
+ EncryptActivity modeInterface = (EncryptActivity) getActivity();
+ EncryptModeFragment modeFragment = modeInterface.getModeFragment();
+
+ if (modeFragment.isAsymmetric()) {
+ long[] encryptionKeyIds = modeFragment.getAsymmetricEncryptionKeyIds();
+ long signingKeyId = modeFragment.getAsymmetricSigningKeyId();
+
+ boolean gotEncryptionKeys = (encryptionKeyIds != null
+ && encryptionKeyIds.length > 0);
+
+ if (!gotEncryptionKeys && signingKeyId == Constants.key.none) {
+ Notify.create(getActivity(), R.string.error_no_encryption_or_signature_key, Notify.Style.ERROR)
+ .show(this);
+ return null;
+ }
+
+ data.setEncryptionMasterKeyIds(encryptionKeyIds);
+ data.setSignatureMasterKeyId(signingKeyId);
+ } else {
+ Passphrase passphrase = modeFragment.getSymmetricPassphrase();
+ if (passphrase == null) {
+ Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
+ .show(this);
+ return null;
+ }
if (passphrase.isEmpty()) {
- passphrase = null;
+ Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
+ .show(this);
+ return null;
}
data.setSymmetricPassphrase(passphrase);
- } else {
- data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
- data.setSignatureMasterKeyId(mSigningKeyId);
}
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();
}
/**
@@ -273,114 +311,44 @@ public class EncryptTextFragment extends CryptoOperationFragment {
sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME);
sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes));
- if (!mSymmetricMode && mEncryptionUserIds != null) {
- Set<String> users = new HashSet<>();
- for (String user : mEncryptionUserIds) {
- KeyRing.UserId userId = KeyRing.splitUserId(user);
- if (userId.email != null) {
- users.add(userId.email);
- }
- }
- // pass trough email addresses as extra for email applications
- sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
+ EncryptActivity modeInterface = (EncryptActivity) getActivity();
+ EncryptModeFragment modeFragment = modeInterface.getModeFragment();
+ if (!modeFragment.isAsymmetric()) {
+ return sendIntent;
}
- return sendIntent;
- }
- protected boolean inputIsValid() {
- if (mMessage == null || mMessage.isEmpty()) {
- Notify.create(getActivity(), R.string.error_empty_text, Notify.Style.ERROR)
- .show(this);
- return false;
+ String[] encryptionUserIds = modeFragment.getAsymmetricEncryptionUserIds();
+ if (encryptionUserIds == null) {
+ return sendIntent;
}
- if (mSymmetricMode) {
- // symmetric encryption checks
-
- if (mSymmetricPassphrase == null) {
- Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
- .show(this);
- return false;
- }
- if (mSymmetricPassphrase.isEmpty()) {
- Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
- .show(this);
- return false;
- }
-
- } else {
- // asymmetric encryption checks
-
- boolean gotEncryptionKeys = (mEncryptionKeyIds != null
- && mEncryptionKeyIds.length > 0);
-
- if (!gotEncryptionKeys && mSigningKeyId == 0) {
- Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
- .show(this);
- return false;
+ Set<String> users = new HashSet<>();
+ for (String user : encryptionUserIds) {
+ KeyRing.UserId userId = KeyRing.splitUserId(user);
+ if (userId.email != null) {
+ users.add(userId.email);
}
}
- return true;
- }
+ // pass trough email addresses as extra for email applications
+ sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
-
- public void startEncrypt(boolean share) {
- mShareAfterEncrypt = share;
- cryptoOperation();
+ return sendIntent;
}
@Override
- protected void cryptoOperation(CryptoInputParcel cryptoInput) {
- if (!inputIsValid()) {
- // Notify was created by inputIsValid.
- return;
- }
+ public void onQueuedOperationSuccess(SignEncryptResult result) {
+ super.onQueuedOperationSuccess(result);
- // Send all information needed to service to edit key in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
-
- final SignEncryptParcel input = createEncryptBundle();
- final Bundle data = new Bundle();
- data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
- data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Message is received after encrypting is done in KeychainIntentService
- ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_encrypting),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (handlePendingMessage(message)) {
- return;
- }
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- SignEncryptResult result =
- message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
-
- if (result.success()) {
- onEncryptSuccess(result);
- } else {
- result.createNotify(getActivity()).show();
- }
- }
- }
- };
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(serviceHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+ hideKeyboard();
- // show progress dialog
- serviceHandler.showProgressDialog(getActivity());
+ if (mShareAfterEncrypt) {
+ // Share encrypted message/file
+ startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
+ } else {
+ // Copy to clipboard
+ copyToClipboard(result);
+ }
- // start service with intent
- getActivity().startService(intent);
}
}
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..7a1e167bb 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,14 +51,11 @@ 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);
}
- // no flickering when clicking textview for Android < 4
- aboutTextView.setTextColor(getResources().getColor(android.R.color.black));
-
return view;
}
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..15098b8d6 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,15 +66,13 @@ 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);
}
- // no flickering when clicking textview for Android < 4
- text.setTextColor(getResources().getColor(android.R.color.black));
-
return scroller;
}
}
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 4cba62d5b..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,12 +17,11 @@
package org.sufficientlysecure.keychain.ui;
-import android.app.ProgressDialog;
+import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
-import android.os.Messenger;
import android.support.v4.app.Fragment;
import android.view.View;
import android.view.View.OnClickListener;
@@ -35,11 +34,10 @@ import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
-import org.sufficientlysecure.keychain.service.CloudImportService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
@@ -49,7 +47,8 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize
import java.io.IOException;
import java.util.ArrayList;
-public class ImportKeysActivity extends BaseNfcActivity {
+public class ImportKeysActivity extends BaseNfcActivity
+ implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY;
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER;
@@ -84,10 +83,17 @@ public class ImportKeysActivity extends BaseNfcActivity {
private Fragment mTopFragment;
private View mImportButton;
+ // for CryptoOperationHelper.Callback
+ private String mKeyserver;
+ private ArrayList<ParcelableKeyRing> mKeyList;
+
+ private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setFullScreenDialogClose(Activity.RESULT_CANCELED, true);
mImportButton = findViewById(R.id.import_import);
mImportButton.setOnClickListener(new OnClickListener() {
@Override
@@ -220,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
@@ -265,7 +271,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
// 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) {
+ if (mListFragment != null) {
return;
}
@@ -285,7 +291,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
// 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) {
+ if (mTopFragment != null) {
return;
}
@@ -312,11 +318,12 @@ 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.
- if (savedInstanceState != null) {
+ if (mTopFragment != null) {
return;
}
@@ -342,7 +349,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
}
}
- public void loadCallback(ImportKeysListFragment.LoaderState loaderState) {
+ public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) {
mListFragment.loadNew(loaderState);
}
@@ -383,32 +390,20 @@ public class ImportKeysActivity extends BaseNfcActivity {
* Import keys with mImportData
*/
public void importKeys() {
- ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState();
- if (ls instanceof ImportKeysListFragment.BytesLoaderState) {
- Log.d(Constants.TAG, "importKeys started");
-
- ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
- this,
- getString(R.string.progress_importing),
- ProgressDialog.STYLE_HORIZONTAL,
- true,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- ImportKeysActivity.this.handleMessage(message);
- }
- };
- // TODO: Currently not using CloudImport here due to https://github.com/open-keychain/open-keychain/issues/1221
- // Send all information needed to service to import key in other thread
- Intent intent = new Intent(this, KeychainIntentService.class);
+ if (mListFragment.getSelectedEntries().size() == 0) {
+ Notify.create(this, R.string.error_nothing_import_selected, Notify.Style.ERROR)
+ .show((ViewGroup) findViewById(R.id.import_snackbar));
+ return;
+ }
- intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
+ mOperationHelper = new CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult>(
+ 1, this, this, R.string.progress_importing
+ );
- // fill values for this action
- Bundle data = new Bundle();
+ ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState();
+ if (ls instanceof ImportKeysListFragment.BytesLoaderState) {
+ Log.d(Constants.TAG, "importKeys started");
// get DATA from selected key entries
IteratorWithSize<ParcelableKeyRing> selectedEntries = mListFragment.getSelectedData();
@@ -423,46 +418,18 @@ public class ImportKeysActivity extends BaseNfcActivity {
new ParcelableFileCache<>(this, "key_import.pcl");
cache.writeCache(selectedEntries);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(serviceHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- serviceHandler.showProgressDialog(this);
+ mKeyList = null;
+ mKeyserver = null;
+ mOperationHelper.cryptoOperation();
- // start service with intent
- startService(intent);
} catch (IOException e) {
Log.e(Constants.TAG, "Problem writing cache file", e);
Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR)
.show((ViewGroup) findViewById(R.id.import_snackbar));
}
} else if (ls instanceof ImportKeysListFragment.CloudLoaderState) {
- ImportKeysListFragment.CloudLoaderState sls = (ImportKeysListFragment.CloudLoaderState) ls;
-
- ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
- this,
- getString(R.string.progress_importing),
- ProgressDialog.STYLE_HORIZONTAL,
- true,
- ProgressDialogFragment.ServiceType.CLOUD_IMPORT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- ImportKeysActivity.this.handleMessage(message);
- }
- };
-
- // Send all information needed to service to query keys in other thread
- Intent intent = new Intent(this, CloudImportService.class);
-
- // fill values for this action
- Bundle data = new Bundle();
-
- data.putString(CloudImportService.IMPORT_KEY_SERVER, sls.mCloudPrefs.keyserver);
+ ImportKeysListFragment.CloudLoaderState sls =
+ (ImportKeysListFragment.CloudLoaderState) ls;
// get selected key entries
ArrayList<ParcelableKeyRing> keys = new ArrayList<>();
@@ -475,23 +442,70 @@ public class ImportKeysActivity extends BaseNfcActivity {
);
}
}
- data.putParcelableArrayList(CloudImportService.IMPORT_KEY_LIST, keys);
- intent.putExtra(CloudImportService.EXTRA_DATA, data);
+ mKeyList = keys;
+ mKeyserver = sls.mCloudPrefs.keyserver;
+ mOperationHelper.cryptoOperation();
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(serviceHandler);
- intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger);
+ }
+ }
- // show progress dialog
- serviceHandler.showProgressDialog(this);
+ @Override
+ protected void onNfcPostExecute() throws IOException {
+ // either way, finish after NFC AsyncTask
+ finish();
+ }
- // start service with intent
- startService(intent);
- } else {
- Notify.create(this, R.string.error_nothing_import, Notify.Style.ERROR)
- .show((ViewGroup) findViewById(R.id.import_snackbar));
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mOperationHelper == null ||
+ !mOperationHelper.handleActivityResult(requestCode, resultCode, data)) {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ 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();
+ intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
+ ImportKeysActivity.this.setResult(RESULT_OK, intent);
+ ImportKeysActivity.this.finish();
+ return;
}
+ if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) {
+ ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData);
+ ImportKeysActivity.this.finish();
+ return;
+ }
+
+ result.createNotify(ImportKeysActivity.this)
+ .show((ViewGroup) findViewById(R.id.import_snackbar));
+ }
+ // methods from CryptoOperationHelper.Callback
+
+ @Override
+ public ImportKeyringParcel createOperationInput() {
+ return new ImportKeyringParcel(mKeyList, mKeyserver);
}
+ @Override
+ public void onCryptoOperationSuccess(ImportKeyResult result) {
+ handleResult(result);
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+ // do nothing
+ }
+
+ @Override
+ public void onCryptoOperationError(ImportKeyResult result) {
+ handleResult(result);
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
index 538fa16c7..746c75600 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
@@ -64,8 +64,8 @@ public class ImportKeysFileFragment extends Fragment {
// open .asc or .gpg files
// setting it to text/plain prevents Cyanogenmod's file manager from selecting asc
// or gpg types!
- FileHelper.openFile(ImportKeysFileFragment.this, Uri.fromFile(Constants.Path.APP_DIR),
- "*/*", REQUEST_CODE_FILE);
+ FileHelper.openDocument(ImportKeysFileFragment.this,
+ Uri.fromFile(Constants.Path.APP_DIR), "*/*", false, REQUEST_CODE_FILE);
}
});
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..8502798cd 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,52 @@ 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() {
+ OrbotHelper.DialogActions dialogActions =
+ new OrbotHelper.DialogActions() {
+ @Override
+ public void onOrbotStarted() {
+ mShowingOrbotDialog = false;
+ restartLoaders();
+ }
+
+ @Override
+ public void onNeutralButton() {
+ mParcelableProxy = ParcelableProxy
+ .getForNoProxy();
+ mShowingOrbotDialog = false;
+ restartLoaders();
+ }
+
+ @Override
+ public void onCancel() {
+ mShowingOrbotDialog = false;
+ }
+ };
+
+ if (OrbotHelper.putOrbotInRequiredState(dialogActions,
+ getActivity())) {
+ // looks like we didn't have to show the
+ // dialog after all
+ mShowingOrbotDialog = false;
+ 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 dc8752d1a..b60f3984c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
@@ -18,7 +18,6 @@
package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
-import android.app.ProgressDialog;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.net.Uri;
@@ -26,8 +25,6 @@ import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
import android.os.Parcelable;
import android.support.v4.app.FragmentActivity;
import android.widget.Toast;
@@ -41,10 +38,10 @@ import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
@@ -55,7 +52,8 @@ import java.util.Locale;
/**
* Proxy activity (just a transparent content view) to scan QR Codes using the Barcode Scanner app
*/
-public class ImportKeysProxyActivity extends FragmentActivity {
+public class ImportKeysProxyActivity extends FragmentActivity
+ implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
public static final String ACTION_QR_CODE_API = OpenKeychainIntents.IMPORT_KEY_FROM_QR_CODE;
// implies activity returns scanned fingerprint as extra and does not import
@@ -64,6 +62,11 @@ public class ImportKeysProxyActivity extends FragmentActivity {
public static final String EXTRA_FINGERPRINT = "fingerprint";
+ // for CryptoOperationHelper
+ private String mKeyserver;
+ private ArrayList<ParcelableKeyRing> mKeyList;
+ private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mImportOpHelper;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -106,6 +109,19 @@ public class ImportKeysProxyActivity extends FragmentActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mImportOpHelper != null) {
+ 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) {
IntentResult scanResult = IntentIntegratorSupportV4.parseActivityResult(requestCode,
resultCode, data);
@@ -121,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) {
@@ -141,24 +150,34 @@ public class ImportKeysProxyActivity extends FragmentActivity {
Log.d(Constants.TAG, "scanned: " + uri);
// example: openpgp4fpr:73EE2314F65FA92EC2390D3A718C070100012282
- if (uri != null && uri.getScheme() != null && uri.getScheme().toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) {
- String fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH);
-
- if (ACTION_SCAN_WITH_RESULT.equals(action)) {
- Intent result = new Intent();
- result.putExtra(EXTRA_FINGERPRINT, fingerprint);
- setResult(RESULT_OK, result);
- finish();
- } else {
- importKeys(fingerprint);
- }
- } else {
+ if (uri == null || uri.getScheme() == null ||
+ !uri.getScheme().toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) {
SingletonResult result = new SingletonResult(
- SingletonResult.RESULT_ERROR, OperationResult.LogType.MSG_WRONG_QR_CODE);
+ SingletonResult.RESULT_ERROR, LogType.MSG_WRONG_QR_CODE);
Intent intent = new Intent();
intent.putExtra(SingletonResult.EXTRA_RESULT, result);
returnResult(intent);
+ return;
+ }
+ 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);
+ Intent intent = new Intent();
+ intent.putExtra(SingletonResult.EXTRA_RESULT, result);
+ returnResult(intent);
+ return;
+ }
+
+ if (ACTION_SCAN_WITH_RESULT.equals(action)) {
+ Intent result = new Intent();
+ result.putExtra(EXTRA_FINGERPRINT, fingerprint);
+ setResult(RESULT_OK, result);
+ finish();
+ } else {
+ importKeys(fingerprint);
}
+
}
public void returnResult(Intent data) {
@@ -194,77 +213,55 @@ public class ImportKeysProxyActivity extends FragmentActivity {
private void startImportService(ArrayList<ParcelableKeyRing> keyRings) {
- // Message is received after importing is done in KeychainIntentService
- ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
- this,
- getString(R.string.progress_importing),
- ProgressDialog.STYLE_HORIZONTAL,
- true,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
- if (returnData == null) {
- finish();
- return;
- }
- final ImportKeyResult result =
- returnData.getParcelable(OperationResult.EXTRA_RESULT);
- if (result == null) {
- Log.e(Constants.TAG, "result == null");
- finish();
- return;
- }
-
- if (!result.success()) {
- // only return if no success...
- Intent data = new Intent();
- data.putExtras(returnData);
- returnResult(data);
- return;
- }
-
- Intent certifyIntent = new Intent(ImportKeysProxyActivity.this,
- CertifyKeyActivity.class);
- certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result);
- certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS,
- result.getImportedMasterKeyIds());
- startActivityForResult(certifyIntent, 0);
- }
- }
- };
-
- // fill values for this action
- Bundle data = new Bundle();
-
// search config
{
Preferences prefs = Preferences.getPreferences(this);
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
- data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
+ mKeyserver = cloudPrefs.keyserver;
}
- data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyRings);
+ mKeyList = keyRings;
- // Send all information needed to service to query keys in other thread
- Intent intent = new Intent(this, KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+ mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_importing);
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(serviceHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+ mImportOpHelper.cryptoOperation();
+ }
+
+
+ // CryptoOperationHelper.Callback methods
- // show progress dialog
- serviceHandler.showProgressDialog(this);
+ @Override
+ public ImportKeyringParcel createOperationInput() {
+ return new ImportKeyringParcel(mKeyList, mKeyserver);
+ }
- // start service with intent
- startService(intent);
+ @Override
+ public void onCryptoOperationSuccess(ImportKeyResult result) {
+ Intent certifyIntent = new Intent(this, CertifyKeyActivity.class);
+ certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result);
+ certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS,
+ result.getImportedMasterKeyIds());
+ startActivityForResult(certifyIntent, 0);
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+
+ }
+
+ @Override
+ public void onCryptoOperationError(ImportKeyResult result) {
+ Bundle returnData = new Bundle();
+ returnData.putParcelable(OperationResult.EXTRA_RESULT, result);
+ Intent data = new Intent();
+ data.putExtras(returnData);
+ returnResult(data);
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
}
/**
@@ -276,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 d8c3e0350..ce6994ba4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
@@ -21,22 +21,21 @@ package org.sufficientlysecure.keychain.ui;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
-import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MergeCursor;
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;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
+import android.text.TextUtils;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -52,51 +51,45 @@ 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;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.service.CloudImportService;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
+import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
-import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.service.PassphraseCacheService;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
+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.util.ExportHelper;
import org.sufficientlysecure.keychain.util.FabContainer;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
+import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
+import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
-import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
-import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
-
/**
* Public key list with sticky list headers. It does _not_ extend ListFragment because it uses
* StickyListHeaders library which does not extend upon ListView.
*/
public class KeyListFragment extends LoaderFragment
implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener,
- LoaderManager.LoaderCallbacks<Cursor>, FabContainer {
-
- static final int REQUEST_REPEAT_PASSPHRASE = 1;
- static final int REQUEST_ACTION = 2;
+ LoaderManager.LoaderCallbacks<Cursor>, FabContainer,
+ CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
- 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;
@@ -108,17 +101,13 @@ public class KeyListFragment extends LoaderFragment
private FloatingActionsMenu mFab;
- // This ids for multiple key export.
- private ArrayList<Long> mIdsForRepeatAskPassphrase;
- // This index for remembering the number of master key.
- private int mIndex;
+ // for CryptoOperationHelper import
+ private ArrayList<ParcelableKeyRing> mKeyList;
+ private String mKeyserver;
+ private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mImportOpHelper;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mExportHelper = new ExportHelper(getActivity());
- }
+ // for ConsolidateOperation
+ private CryptoOperationHelper<ConsolidateInputParcel, ConsolidateResult> mConsolidateOpHelper;
/**
* Load custom layout with StickyListView from library
@@ -230,19 +219,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;
}
}
@@ -289,7 +266,6 @@ public class KeyListFragment extends LoaderFragment
static final String ORDER =
KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC";
-
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
@@ -322,6 +298,22 @@ public class KeyListFragment extends LoaderFragment
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.setSearchQuery(mQuery);
+
+ if (data != null && (mQuery == null || TextUtils.isEmpty(mQuery))) {
+ boolean isSecret = data.moveToFirst() && data.getInt(KeyListAdapter.INDEX_HAS_ANY_SECRET) != 0;
+ if (!isSecret) {
+ MatrixCursor headerCursor = new MatrixCursor(KeyListAdapter.PROJECTION);
+ Long[] row = new Long[KeyListAdapter.PROJECTION.length];
+ row[KeyListAdapter.INDEX_HAS_ANY_SECRET] = 1L;
+ row[KeyListAdapter.INDEX_MASTER_KEY_ID] = 0L;
+ headerCursor.addRow(row);
+
+ Cursor dataCursor = data;
+ data = new MergeCursor(new Cursor[] {
+ headerCursor, dataCursor
+ });
+ }
+ }
mAdapter.swapCursor(data);
mStickyList.setAdapter(mAdapter);
@@ -358,7 +350,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) {
@@ -376,38 +368,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);
}
@@ -462,18 +431,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);
@@ -504,6 +465,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);
}
@@ -555,9 +520,12 @@ public class KeyListFragment extends LoaderFragment
}
private void updateAllKeys() {
- Context context = getActivity();
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
- ProviderHelper providerHelper = new ProviderHelper(context);
+ ProviderHelper providerHelper = new ProviderHelper(activity);
Cursor cursor = providerHelper.getContentResolver().query(
KeyRings.buildUnifiedKeyRingsUri(), new String[]{
@@ -565,182 +533,115 @@ public class KeyListFragment extends LoaderFragment
}, null, null, null
);
- ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
-
- while (cursor.moveToNext()) {
- byte[] blob = cursor.getBlob(0);//fingerprint column is 0
- String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
- ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
- keyList.add(keyEntry);
+ if (cursor == null) {
+ Notify.create(activity, R.string.error_loading_keys, Notify.Style.ERROR);
+ return;
}
- ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_updating),
- ProgressDialog.STYLE_HORIZONTAL,
- true,
- ProgressDialogFragment.ServiceType.CLOUD_IMPORT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
- if (returnData == null) {
- return;
- }
- final ImportKeyResult result =
- returnData.getParcelable(OperationResult.EXTRA_RESULT);
- if (result == null) {
- Log.e(Constants.TAG, "result == null");
- return;
- }
-
- result.createNotify(getActivity()).show();
- }
+ ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
+ try {
+ while (cursor.moveToNext()) {
+ byte[] blob = cursor.getBlob(0);//fingerprint column is 0
+ String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
+ ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
+ keyList.add(keyEntry);
}
- };
-
- // Send all information needed to service to query keys in other thread
- Intent intent = new Intent(getActivity(), CloudImportService.class);
-
- // fill values for this action
- Bundle data = new Bundle();
+ mKeyList = keyList;
+ } finally {
+ cursor.close();
+ }
// search config
{
Preferences prefs = Preferences.getPreferences(getActivity());
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
- data.putString(CloudImportService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
+ mKeyserver = cloudPrefs.keyserver;
}
- data.putParcelableArrayList(CloudImportService.IMPORT_KEY_LIST, keyList);
-
- intent.putExtra(CloudImportService.EXTRA_DATA, data);
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(serviceHandler);
- intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- serviceHandler.showProgressDialog(getActivity());
-
- // start service with intent
- getActivity().startService(intent);
+ mImportOpHelper = new CryptoOperationHelper<>(1, this,
+ this, R.string.progress_updating);
+ mImportOpHelper.cryptoOperation();
}
private void consolidate() {
- // Message is received after importing is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_importing),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
- if (returnData == null) {
- return;
- }
- final ConsolidateResult result =
- returnData.getParcelable(OperationResult.EXTRA_RESULT);
- if (result == null) {
- return;
- }
- result.createNotify(getActivity()).show();
- }
+ CryptoOperationHelper.Callback<ConsolidateInputParcel, ConsolidateResult> callback
+ = new CryptoOperationHelper.Callback<ConsolidateInputParcel, ConsolidateResult>() {
+
+ @Override
+ public ConsolidateInputParcel createOperationInput() {
+ return new ConsolidateInputParcel(false); // we want to perform a full consolidate
}
- };
- // Send all information needed to service to import key in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
+ @Override
+ public void onCryptoOperationSuccess(ConsolidateResult result) {
+ result.createNotify(getActivity()).show();
+ }
- intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE);
+ @Override
+ public void onCryptoOperationCancelled() {
- // fill values for this action
- Bundle data = new Bundle();
+ }
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+ @Override
+ public void onCryptoOperationError(ConsolidateResult result) {
+ result.createNotify(getActivity()).show();
+ }
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
+ };
- // show progress dialog
- saveHandler.showProgressDialog(getActivity());
+ mConsolidateOpHelper =
+ new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing);
- // start service with intent
- getActivity().startService(intent);
+ 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;
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mImportOpHelper != null) {
+ mImportOpHelper.handleActivityResult(requestCode, resultCode, data);
}
- long[] idsForMultiExport = new long[mIdsForRepeatAskPassphrase.size()];
- for (int i = 0; i < mIdsForRepeatAskPassphrase.size(); ++i) {
- idsForMultiExport[i] = mIdsForRepeatAskPassphrase.get(i);
+
+ if (mConsolidateOpHelper != null) {
+ mConsolidateOpHelper.handleActivityResult(requestCode, resultCode, data);
}
- 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);
- }
+ 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;
- @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;
- }
- 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());
- }
+ 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;
- 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_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;
}
}
@@ -761,12 +662,41 @@ public class KeyListFragment extends LoaderFragment
anim.start();
}
+ // CryptoOperationHelper.Callback methods
+ @Override
+ public ImportKeyringParcel createOperationInput() {
+ return new ImportKeyringParcel(mKeyList, mKeyserver);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(ImportKeyResult result) {
+ result.createNotify(getActivity()).show();
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+
+ }
+
+ @Override
+ public void onCryptoOperationError(ImportKeyResult result) {
+ result.createNotify(getActivity()).show();
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
+
public class KeyListAdapter extends KeyAdapter implements StickyListHeadersAdapter {
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
@@ -795,9 +725,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);
@@ -806,6 +738,29 @@ public class KeyListFragment extends LoaderFragment
return v;
}
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
+ long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
+ if (isSecret && masterKeyId == 0L) {
+
+ // sort of a hack: if this item isn't enabled, we make it clickable
+ // to intercept its click events
+ view.setClickable(true);
+
+ KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
+ h.setDummy(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ createKey();
+ }
+ });
+ return;
+ }
+
+ super.bindView(view, context, cursor);
+ }
+
private class HeaderViewHolder {
TextView mText;
TextView mCount;
@@ -844,6 +799,10 @@ public class KeyListFragment extends LoaderFragment
if (mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0) {
{ // set contact count
int num = mCursor.getCount();
+ // If this is a dummy secret key, subtract one
+ if (mCursor.getLong(INDEX_MASTER_KEY_ID) == 0L) {
+ num -= 1;
+ }
String contactsTotal = mContext.getResources().getQuantityString(R.plurals.n_keys, num, num);
holder.mCount.setText(contactsTotal);
holder.mCount.setVisibility(View.VISIBLE);
@@ -902,8 +861,9 @@ public class KeyListFragment extends LoaderFragment
public boolean isAnySecretSelected() {
for (int pos : mSelection.keySet()) {
- if (isSecretAvailable(pos))
+ if (isSecretAvailable(pos)) {
return true;
+ }
}
return false;
}
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 138f2f4e7..4de83337e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java
@@ -43,13 +43,13 @@ 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;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
-import java.util.Iterator;
public class LogDisplayFragment extends ListFragment implements OnItemClickListener {
@@ -58,9 +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);
}
@@ -75,7 +78,12 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
return;
}
- mResult = intent.getParcelableExtra(EXTRA_RESULT);
+ if (savedInstanceState != null) {
+ mResult = savedInstanceState.getParcelable(EXTRA_RESULT);
+ } else {
+ mResult = intent.getParcelableExtra(EXTRA_RESULT);
+ }
+
if (mResult == null) {
getActivity().finish();
return;
@@ -92,6 +100,14 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
}
@Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ // need to parcel this again, logs are only single-instance parcelable
+ outState.putParcelable(EXTRA_RESULT, mResult);
+ }
+
+ @Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.log_display, menu);
@@ -110,7 +126,6 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
}
private void exportLog() {
-
showExportLogDialog(new File(Constants.Path.APP_DIR, "export.log"));
}
@@ -142,7 +157,9 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
}
}
- if (!error) currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_SUCCESS, 1);
+ if (!error) {
+ currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_SUCCESS, 1);
+ }
int opResultCode = error ? OperationResult.RESULT_ERROR : OperationResult.RESULT_OK;
OperationResult opResult = new LogExportResult(opResultCode, currLog);
@@ -158,8 +175,8 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
*/
private String getPrintableOperationLog(OperationResult.OperationLog opLog, String basePadding) {
String log = "";
- for (Iterator<LogEntryParcel> logIterator = opLog.iterator(); logIterator.hasNext(); ) {
- log += getPrintableLogEntry(logIterator.next(), basePadding) + "\n";
+ for (LogEntryParcel anOpLog : opLog) {
+ log += getPrintableLogEntry(anOpLog, basePadding) + "\n";
}
log = log.substring(0, log.length() - 1);//gets rid of extra new line
return log;
@@ -235,7 +252,7 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
String message = this.getString(R.string.specify_file_to_export_log_to);
- FileHelper.saveFile(new FileHelper.FileDialogCallback() {
+ FileHelper.saveDocumentDialog(new FileHelper.FileDialogCallback() {
@Override
public void onFileSelected(File file, boolean checked) {
writeToLogFile(mResult.getLog(), file);
@@ -343,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;
}
@@ -374,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 f571ba1e6..6f5d98afd 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
@@ -23,8 +23,8 @@ import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.support.v4.app.FragmentTransaction;
-import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.AdapterView;
@@ -33,23 +33,31 @@ 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;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.remote.ui.AppsListFragment;
+import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.util.FabContainer;
import org.sufficientlysecure.keychain.util.Preferences;
-public class MainActivity extends AppCompatActivity implements FabContainer {
+public class MainActivity extends BaseNfcActivity implements FabContainer, OnBackStackChangedListener {
- public Drawer.Result result;
+ static final int ID_KEYS = 1;
+ static final int ID_ENCRYPT_DECRYPT = 2;
+ static final int ID_APPS = 3;
+ static final int ID_BACKUP = 4;
+ static final int ID_SETTINGS = 5;
+ static final int ID_HELP = 6;
- private KeyListFragment mKeyListFragment ;
- private AppsListFragment mAppsListFragment;
- private EncryptDecryptOverviewFragment mEncryptDecryptOverviewFragment;
- private Fragment mLastUsedFragment;
+ // 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 mDrawer;
private Toolbar mToolbar;
@Override
@@ -57,50 +65,51 @@ public class MainActivity extends AppCompatActivity implements FabContainer {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
- //initialize FragmentLayout with KeyListFragment at first
- Fragment mainFragment = new KeyListFragment();
- FragmentManager fm = getSupportFragmentManager();
- FragmentTransaction transaction = fm.beginTransaction();
- transaction.replace(R.id.main_fragment_container, mainFragment);
- transaction.commit();
-
mToolbar = (Toolbar) findViewById(R.id.toolbar);
mToolbar.setTitle(R.string.app_name);
setSupportActionBar(mToolbar);
- result = new Drawer()
+ mDrawer = new DrawerBuilder()
.withActivity(this)
.withHeader(R.layout.main_drawer_header)
.withToolbar(mToolbar)
.addDrawerItems(
- new PrimaryDrawerItem().withName(R.string.nav_keys).withIcon(CommunityMaterial.Icon.cmd_key).withIdentifier(1).withCheckable(false),
- new PrimaryDrawerItem().withName(R.string.nav_encrypt_decrypt).withIcon(FontAwesome.Icon.faw_lock).withIdentifier(2).withCheckable(false),
- new PrimaryDrawerItem().withName(R.string.title_api_registered_apps).withIcon(CommunityMaterial.Icon.cmd_apps).withIdentifier(3).withCheckable(false)
+ new PrimaryDrawerItem().withName(R.string.nav_keys).withIcon(CommunityMaterial.Icon.cmd_key)
+ .withIdentifier(ID_KEYS).withCheckable(false),
+ 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),
+ 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
- new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(4).withCheckable(false),
- new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(5).withCheckable(false)
+ new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withCheckable(false),
+ new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withCheckable(false)
)
.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()) {
- case 1:
+ case ID_KEYS:
onKeysSelected();
break;
- case 2:
+ case ID_ENCRYPT_DECRYPT:
onEnDecryptSelected();
break;
- case 3:
+ case ID_APPS:
onAppsSelected();
break;
- case 4:
+ case ID_BACKUP:
+ onBackupSelected();
+ break;
+ case ID_SETTINGS:
intent = new Intent(MainActivity.this, SettingsActivity.class);
break;
- case 5:
+ case ID_HELP:
intent = new Intent(MainActivity.this, HelpActivity.class);
break;
}
@@ -108,6 +117,8 @@ public class MainActivity extends AppCompatActivity implements FabContainer {
MainActivity.this.startActivity(intent);
}
}
+
+ return false;
}
})
.withSelectedItem(-1)
@@ -116,7 +127,7 @@ public class MainActivity extends AppCompatActivity implements FabContainer {
// if this is the first time show first time activity
Preferences prefs = Preferences.getPreferences(this);
- if (prefs.isFirstTime()) {
+ if (!getIntent().getBooleanExtra(EXTRA_SKIP_FIRST_TIME, false) && prefs.isFirstTime()) {
Intent intent = new Intent(this, CreateKeyActivity.class);
intent.putExtra(CreateKeyActivity.EXTRA_FIRST_TIME, true);
startActivity(intent);
@@ -124,82 +135,91 @@ public class MainActivity extends AppCompatActivity implements FabContainer {
return;
}
+ 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)) {
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
result.createNotify(this).show();
}
- }
- private void clearFragments() {
- mKeyListFragment = null;
- mAppsListFragment = null;
- mEncryptDecryptOverviewFragment = null;
+ // always initialize keys fragment to the bottom of the backstack
+ onKeysSelected();
- getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
- }
+ 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;
+ }
+ }
- private void setFragment(Fragment fragment) {
- setFragment(fragment, true);
}
private void setFragment(Fragment fragment, boolean addToBackStack) {
- this.mLastUsedFragment = fragment;
- FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+
+ FragmentTransaction ft = fragmentManager.beginTransaction();
ft.replace(R.id.main_fragment_container, fragment);
if (addToBackStack) {
ft.addToBackStack(null);
}
ft.commit();
+
}
- private boolean onKeysSelected() {
+ private void onKeysSelected() {
mToolbar.setTitle(R.string.app_name);
- clearFragments();
-
- if (mKeyListFragment == null) {
- mKeyListFragment = new KeyListFragment();
- }
-
- setFragment(mKeyListFragment, false);
- return true;
+ mDrawer.setSelectionByIdentifier(ID_KEYS, false);
+ Fragment frag = new KeyListFragment();
+ setFragment(frag, false);
}
- private boolean onEnDecryptSelected() {
+ private void onEnDecryptSelected() {
mToolbar.setTitle(R.string.nav_encrypt_decrypt);
- clearFragments();
- if (mEncryptDecryptOverviewFragment == null) {
- mEncryptDecryptOverviewFragment = new EncryptDecryptOverviewFragment();
- }
-
- setFragment(mEncryptDecryptOverviewFragment);
- return true;
+ mDrawer.setSelectionByIdentifier(ID_ENCRYPT_DECRYPT, false);
+ Fragment frag = new EncryptDecryptOverviewFragment();
+ setFragment(frag, true);
}
- private boolean onAppsSelected() {
+ private void onAppsSelected() {
mToolbar.setTitle(R.string.nav_apps);
- clearFragments();
- if (mAppsListFragment == null) {
- mAppsListFragment = new AppsListFragment();
- }
+ mDrawer.setSelectionByIdentifier(ID_APPS, false);
+ Fragment frag = new AppsListFragment();
+ setFragment(frag, true);
+ }
- setFragment(mAppsListFragment);
- return 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 = result.saveInstanceState(outState);
+ // add the values which need to be saved from the drawer to the bundle
+ outState = mDrawer.saveInstanceState(outState);
super.onSaveInstanceState(outState);
}
@Override
- public void onBackPressed(){
- //handle the back press :D close the drawer first and if the drawer is closed close the activity
- if (result != null && result.isDrawerOpen()) {
- result.closeDrawer();
+ public void onBackPressed() {
+ // close the drawer first and if the drawer is closed do regular backstack handling
+ if (mDrawer != null && mDrawer.isDrawerOpen()) {
+ mDrawer.closeDrawer();
} else {
super.onBackPressed();
}
@@ -223,4 +243,32 @@ public class MainActivity extends AppCompatActivity implements FabContainer {
}
}
+
+ @Override
+ public void onBackStackChanged() {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ if (fragmentManager == null) {
+ return;
+ }
+ Fragment frag = fragmentManager.findFragmentById(R.id.main_fragment_container);
+ if (frag == null) {
+ return;
+ }
+
+ // make sure the selected icon is the one shown at this point
+ if (frag instanceof KeyListFragment) {
+ mToolbar.setTitle(R.string.app_name);
+ mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_KEYS), false);
+ } else if (frag instanceof EncryptDecryptOverviewFragment) {
+ mToolbar.setTitle(R.string.nav_encrypt_decrypt);
+ mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false);
+ } else if (frag instanceof AppsListFragment) {
+ 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 aa66053fa..b811b218e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java
@@ -1,52 +1,120 @@
-/**
- * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
+/*
+ * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2013-2014 Signe Rüsch
+ * Copyright (C) 2013-2014 Philipp Jakubeit
+ *
+ * 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.
*
- * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
+ * 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.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;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
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.base.BaseNfcActivity;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.OrientationUtils;
+import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
/**
* 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
*/
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 initTheme() {
+ mThemeChanger = new ThemeChanger(this);
+ mThemeChanger.setThemes(R.style.Theme_Keychain_Light_Dialog,
+ R.style.Theme_Keychain_Dark_Dialog);
+ mThemeChanger.changeTheme();
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(Constants.TAG, "NfcOperationActivity.onCreate");
+ // prevent annoying orientation changes while fumbling with the device
+ OrientationUtils.lockOrientation(this);
+
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
+
+ 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();
@@ -54,49 +122,186 @@ public class NfcOperationActivity extends BaseNfcActivity {
mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT);
// obtain passphrase for this subkey
- obtainYubiKeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput));
+ if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD) {
+ obtainYubiKeyPin(mRequiredInput);
+ }
}
@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];
+ mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime);
+
+ 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 {
+ secretKeyRing = providerHelper.getCanonicalizedSecretKeyRing(
+ KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mRequiredInput.getMasterKeyId())
+ );
+ } catch (ProviderHelper.NotFoundException e) {
+ throw new IOException("Couldn't find subkey for key to card operation.");
+ }
+
+ 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();
+
+ CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId);
+
+ long keyGenerationTimestampMillis = key.getCreationTime().getTime();
+ long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000;
+ byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array();
+ byte[] cardSerialNumber = Arrays.copyOf(nfcGetAid(), 16);
+
+ Passphrase passphrase;
+ try {
+ passphrase = PassphraseCacheService.getCachedPassphrase(this,
+ mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
+ } catch (PassphraseCacheService.KeyNotFoundException e) {
+ throw new IOException("Unable to get cached passphrase!");
+ }
+
+ if (key.canSign() || key.canCertify()) {
+ if (shouldPutKey(key.getFingerprint(), 0)) {
+ nfcPutKey(0xB6, key, passphrase);
+ nfcPutData(0xCE, timestampBytes);
+ nfcPutData(0xC7, key.getFingerprint());
+ } else {
+ throw new IOException("Key slot occupied; card must be reset to put new signature key.");
+ }
+ } else if (key.canEncrypt()) {
+ if (shouldPutKey(key.getFingerprint(), 1)) {
+ nfcPutKey(0xB8, key, passphrase);
+ nfcPutData(0xCF, timestampBytes);
+ nfcPutData(0xC8, key.getFingerprint());
+ } else {
+ throw new IOException("Key slot occupied; card must be reset to put new decryption key.");
+ }
+ } else if (key.canAuthenticate()) {
+ if (shouldPutKey(key.getFingerprint(), 2)) {
+ nfcPutKey(0xA4, key, passphrase);
+ nfcPutData(0xD0, timestampBytes);
+ nfcPutData(0xC9, key.getFingerprint());
+ } else {
+ throw new IOException("Key slot occupied; card must be reset to put new authentication key.");
+ }
+ } else {
+ throw new IOException("Inappropriate key flags for smart card key.");
+ }
+
+ // TODO: Is this really used anywhere?
+ 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
+ // save updated cryptoInputParcel in cache
+ CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, mInputParcel);
setResult(RESULT_OK, mServiceIntent);
} else {
Intent result = new Intent();
- result.putExtra(NfcOperationActivity.RESULT_DATA, inputParcel);
+ // send back the CryptoInputParcel we received
+ result.putExtra(RESULT_CRYPTO_INPUT, mInputParcel);
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 {
+ byte[] cardFingerprint = nfcGetFingerprint(idx);
+ // Slot is empty, or contains this key already. PUT KEY operation is safe
+ if (Arrays.equals(cardFingerprint, BLANK_FINGERPRINT) ||
+ Arrays.equals(cardFingerprint, fingerprint)) {
+ return true;
+ }
+
+ // Slot already contains a different key; don't overwrite it.
+ return false;
}
@Override
@@ -114,8 +319,6 @@ public class NfcOperationActivity extends BaseNfcActivity {
// clear (invalid) passphrase
PassphraseCacheService.clearCachedPassphrase(
this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
-
- obtainYubiKeyPin(RequiredInputParcel.createRequiredPassphrase(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..0e70cda14
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java
@@ -0,0 +1,173 @@
+/*
+ * 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.app.ProgressDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.v4.app.FragmentActivity;
+import android.view.ContextThemeWrapper;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ParcelableProxy;
+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
+ implements OrbotHelper.DialogActions {
+
+ public static final int MESSAGE_ORBOT_STARTED = 1;
+ public static final int MESSAGE_ORBOT_IGNORE = 2;
+ public static final int MESSAGE_DIALOG_CANCEL = 3;
+
+ // if suppplied and true will start Orbot directly without showing dialog
+ public static final String EXTRA_START_ORBOT = "start_orbot";
+ // used for communicating results when triggered from a service
+ public static final String EXTRA_MESSENGER = "messenger";
+
+ // 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;
+ private Messenger mMessenger;
+
+ private ProgressDialog mShowOrbotProgressDialog;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mCryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
+ if (mCryptoInputParcel == null) {
+ // compatibility with usages that don't use a CryptoInputParcel
+ mCryptoInputParcel = new CryptoInputParcel();
+ }
+
+ mMessenger = getIntent().getParcelableExtra(EXTRA_MESSENGER);
+
+ boolean startOrbotDirect = getIntent().getBooleanExtra(EXTRA_START_ORBOT, false);
+ if (startOrbotDirect) {
+ ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(this);
+ mShowOrbotProgressDialog = new ProgressDialog(theme);
+ mShowOrbotProgressDialog.setTitle(R.string.progress_starting_orbot);
+ mShowOrbotProgressDialog.setCancelable(false);
+ mShowOrbotProgressDialog.show();
+ OrbotHelper.bestPossibleOrbotStart(this, this, false);
+ } else {
+ showDialog();
+ }
+ }
+
+ /**
+ * Displays an install or start orbot dialog (or silent orbot start) depending on orbot's
+ * presence and state
+ */
+ public void showDialog() {
+ DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
+ public void run() {
+
+ if (OrbotHelper.putOrbotInRequiredState(OrbotRequiredDialogActivity.this,
+ OrbotRequiredDialogActivity.this)) {
+ // no action required after all
+ onOrbotStarted();
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ switch (requestCode) {
+ case OrbotHelper.START_TOR_RESULT: {
+ dismissOrbotProgressDialog();
+ // unfortunately, this result is returned immediately and not when Orbot is started
+ // 10s is approximately the longest time Orbot has taken to start
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ onOrbotStarted(); // assumption that orbot was started
+ }
+ }, 10000);
+ }
+ }
+ }
+
+ /**
+ * for when Orbot is started without showing the dialog by the EXTRA_START_ORBOT intent extra
+ */
+ private void dismissOrbotProgressDialog() {
+ if (mShowOrbotProgressDialog != null) {
+ mShowOrbotProgressDialog.dismiss();
+ }
+ }
+
+ @Override
+ public void onOrbotStarted() {
+ dismissOrbotProgressDialog();
+ sendMessage(MESSAGE_ORBOT_STARTED);
+ Intent intent = new Intent();
+ // send back unmodified CryptoInputParcel for a retry
+ intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @Override
+ public void onNeutralButton() {
+ sendMessage(MESSAGE_ORBOT_IGNORE);
+ Intent intent = new Intent();
+ mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy());
+ intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @Override
+ public void onCancel() {
+ sendMessage(MESSAGE_DIALOG_CANCEL);
+ finish();
+ }
+
+ private void sendMessage(int what) {
+ if (mMessenger != null) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ try {
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "Could not deliver message", e);
+ }
+ }
+ }
+} \ 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 c6431bfaf..e71349880 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
@@ -17,16 +17,18 @@
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;
@@ -43,20 +45,21 @@ import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
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.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
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;
@@ -64,6 +67,8 @@ 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 {
@@ -71,11 +76,13 @@ public class PassphraseDialogActivity extends FragmentActivity {
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 static final int REQUEST_CODE_ENTER_PATTERN = 2;
+ private CryptoInputParcel mCryptoInputParcel;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -90,20 +97,52 @@ 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)
- long keyId;
if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) {
- keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
+ mSubKeyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
} else {
RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
switch (requiredInput.mType) {
case PASSPHRASE_SYMMETRIC: {
- keyId = Constants.key.symmetric;
+ mSubKeyId = Constants.key.symmetric;
break;
}
case PASSPHRASE: {
- keyId = requiredInput.getSubKeyId();
+
+ // handle empty passphrases by directly returning an empty crypto input parcel
+ try {
+ CanonicalizedSecretKeyRing pubRing =
+ new ProviderHelper(this).getCanonicalizedSecretKeyRing(
+ requiredInput.getMasterKeyId());
+ // use empty passphrase for empty passphrase
+ if (pubRing.getSecretKey(requiredInput.getSubKeyId()).getSecretKeyType() ==
+ SecretKeyType.PASSPHRASE_EMPTY) {
+ // also return passphrase back to activity
+ Intent returnIntent = new Intent();
+ mCryptoInputParcel.mPassphrase = new Passphrase("");
+ returnIntent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
+ setResult(RESULT_OK, returnIntent);
+ finish();
+ return;
+ }
+ } catch (NotFoundException e) {
+ Log.e(Constants.TAG, "Key not found?!", e);
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ mSubKeyId = requiredInput.getSubKeyId();
break;
}
default: {
@@ -112,64 +151,35 @@ public class PassphraseDialogActivity extends FragmentActivity {
}
}
- Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
-
- show(this, keyId, serviceIntent);
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case REQUEST_CODE_ENTER_PATTERN: {
- /*
- * NOTE that there are 4 possible result codes!!!
- */
- switch (resultCode) {
- case RESULT_OK:
- // The user passed
- break;
- case RESULT_CANCELED:
- // The user cancelled the task
- break;
-// case LockPatternActivity.RESULT_FAILED:
-// // The user failed to enter the pattern
-// break;
-// case LockPatternActivity.RESULT_FORGOT_PATTERN:
-// // The user forgot the pattern and invoked your recovery Activity.
-// break;
- }
+ protected void onResumeFragments() {
+ super.onResumeFragments();
- /*
- * In any case, there's always a key EXTRA_RETRY_COUNT, which holds
- * the number of tries that the user did.
- */
-// int retryCount = data.getIntExtra(
-// LockPatternActivity.EXTRA_RETRY_COUNT, 0);
+ /* 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
+ * for a symmetric passphrase
+ */
- break;
- }
- }
+ Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
+
+ PassphraseDialogFragment frag = new PassphraseDialogFragment();
+ Bundle args = new Bundle();
+ args.putLong(EXTRA_SUBKEY_ID, mSubKeyId);
+ args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
+ frag.setArguments(args);
+ frag.show(getSupportFragmentManager(), "passphraseDialog");
}
- /**
- * Shows 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
- * for a symmetric passphrase
- */
- public static void show(final FragmentActivity context, final long keyId, final Intent serviceIntent) {
- DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
- public void run() {
- // do NOT check if the key even needs a passphrase. that's not our job here.
- PassphraseDialogFragment frag = new PassphraseDialogFragment();
- Bundle args = new Bundle();
- args.putLong(EXTRA_SUBKEY_ID, keyId);
- args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
-
- frag.setArguments(args);
-
- frag.show(context.getSupportFragmentManager(), "passphraseDialog");
- }
- });
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag("passphraseDialog");
+ if (dialog != null) {
+ dialog.dismiss();
+ }
}
public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
@@ -183,17 +193,12 @@ public class PassphraseDialogActivity extends FragmentActivity {
private Intent mServiceIntent;
- /**
- * Creates dialog
- */
+ @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);
@@ -246,12 +251,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
userId = null;
}
- /* Get key type for message */
- // find a master key id for our key
- long masterKeyId = new ProviderHelper(activity).getMasterKeyId(mSubKeyId);
- CachedPublicKeyRing keyRing = new ProviderHelper(activity).getCachedPublicKeyRing(masterKeyId);
- // get the type of key (from the database)
- keyType = keyRing.getSecretKeyType(mSubKeyId);
+ keyType = mSecretRing.getSecretKey(mSubKeyId).getSecretKeyType();
switch (keyType) {
case PASSPHRASE:
message = getString(R.string.passphrase_for, userId);
@@ -284,51 +284,42 @@ public class PassphraseDialogActivity extends FragmentActivity {
mPassphraseText.setText(message);
- if (keyType == CanonicalizedSecretKey.SecretKeyType.PATTERN) {
- // start pattern dialog and show progress circle here...
-// Intent patternActivity = new Intent(getActivity(), LockPatternActivity.class);
-// patternActivity.putExtra(LockPatternActivity.EXTRA_PATTERN, "123");
-// startActivityForResult(patternActivity, REQUEST_CODE_ENTER_PATTERN);
- mInput.setVisibility(View.INVISIBLE);
- mProgress.setVisibility(View.VISIBLE);
- } else {
- // 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/
- // Notes: * onCreateView can't be used because we want to add buttons to the dialog
- // * opening in onActivityCreated does not work on Android 4.4
- mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- mPassphraseEditText.post(new Runnable() {
- @Override
- public void run() {
- if (getActivity() == null || mPassphraseEditText == null) {
- return;
- }
- InputMethodManager imm = (InputMethodManager) getActivity()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
+ // 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/
+ // Notes: * onCreateView can't be used because we want to add buttons to the dialog
+ // * opening in onActivityCreated does not work on Android 4.4
+ mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ mPassphraseEditText.post(new Runnable() {
+ @Override
+ public void run() {
+ if (getActivity() == null || mPassphraseEditText == null) {
+ return;
}
- });
- }
- });
- mPassphraseEditText.requestFocus();
-
- 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);
- } else {
- mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ InputMethodManager imm = (InputMethodManager) getActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
+ }
+ });
}
+ });
+ mPassphraseEditText.requestFocus();
+
+ 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())
+ || 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);
}
+ mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
+
AlertDialog dialog = alert.create();
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null);
@@ -347,11 +338,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;
@@ -404,15 +400,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);
@@ -428,9 +433,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
@@ -449,20 +457,16 @@ public class PassphraseDialogActivity extends FragmentActivity {
// note we need no synchronization here, this variable is only accessed in the ui thread
mIsCancelled = true;
+
+ getActivity().setResult(RESULT_CANCELED);
+ getActivity().finish();
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
- if (getActivity() == null) {
- return;
- }
-
hideKeyboard();
-
- getActivity().setResult(RESULT_CANCELED);
- getActivity().finish();
}
private void hideKeyboard() {
@@ -476,11 +480,9 @@ public class PassphraseDialogActivity extends FragmentActivity {
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
- /**
- * Associate the "done" button on the soft keyboard with the okay button in the view
- */
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ // Associate the "done" button on the soft keyboard with the okay button in the view
if (EditorInfo.IME_ACTION_DONE == actionId) {
AlertDialog dialog = ((AlertDialog) getDialog());
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java
deleted file mode 100644
index 2e838535d..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java
+++ /dev/null
@@ -1,575 +0,0 @@
-/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package org.sufficientlysecure.keychain.ui;
-
-import android.annotation.TargetApi;
-import android.app.AlertDialog;
-import android.app.PendingIntent;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.nfc.FormatException;
-import android.nfc.NdefMessage;
-import android.nfc.NdefRecord;
-import android.nfc.NfcAdapter;
-import android.nfc.Tag;
-import android.nfc.tech.Ndef;
-import android.os.Build;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentTransaction;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import org.sufficientlysecure.keychain.R;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.List;
-
-@TargetApi(Build.VERSION_CODES.HONEYCOMB)
-public class PassphraseWizardActivity extends FragmentActivity {
-//public class PassphraseWizardActivity extends FragmentActivity implements LockPatternView.OnPatternListener {
- //create or authenticate
- public String selectedAction;
- //for lockpattern
- public static char[] pattern;
- private static String passphrase = "";
- //nfc string
- private static byte[] output = new byte[8];
-
- public static final String CREATE_METHOD = "create";
- public static final String AUTHENTICATION = "authenticate";
-
- NfcAdapter adapter;
- PendingIntent pendingIntent;
- IntentFilter writeTagFilters[];
- boolean writeMode;
- Tag myTag;
- boolean writeNFC = false;
- boolean readNFC = false;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (getActionBar() != null) {
- getActionBar().setTitle(R.string.unlock_method);
- }
-
- selectedAction = getIntent().getAction();
- if (savedInstanceState == null) {
- SelectMethods selectMethods = new SelectMethods();
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- transaction.add(R.id.fragmentContainer, selectMethods).commit();
- }
- setContentView(R.layout.passphrase_wizard);
-
- adapter = NfcAdapter.getDefaultAdapter(this);
- if (adapter != null) {
- pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, PassphraseWizardActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
- IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
- tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
- writeTagFilters = new IntentFilter[]{tagDetected};
- }
- }
-
- public void noPassphrase(View view) {
- passphrase = "";
- Toast.makeText(this, R.string.no_passphrase_set, Toast.LENGTH_SHORT).show();
- this.finish();
- }
-
- public void passphrase(View view) {
- Passphrase passphrase = new Passphrase();
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- transaction.replace(R.id.fragmentContainer, passphrase).addToBackStack(null).commit();
- }
-
- public void startLockpattern(View view) {
- if (getActionBar() != null) {
- getActionBar().setTitle(R.string.draw_lockpattern);
- }
-// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
-// LockPatternFragment lpf = LockPatternFragment.newInstance("asd");
-
-// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
-// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
- }
-
- public void cancel(View view) {
- this.finish();
- }
-
- public void savePassphrase(View view) {
- EditText passphrase = (EditText) findViewById(R.id.passphrase);
- passphrase.setError(null);
- String pw = passphrase.getText().toString();
- //check and save passphrase
- if (selectedAction.equals(CREATE_METHOD)) {
- EditText passphraseAgain = (EditText) findViewById(R.id.passphraseAgain);
- passphraseAgain.setError(null);
- String pwAgain = passphraseAgain.getText().toString();
-
- if (!TextUtils.isEmpty(pw)) {
- if (!TextUtils.isEmpty(pwAgain)) {
- if (pw.equals(pwAgain)) {
- PassphraseWizardActivity.passphrase = pw;
- Toast.makeText(this, getString(R.string.passphrase_saved), Toast.LENGTH_SHORT).show();
- this.finish();
- } else {
- passphrase.setError(getString(R.string.passphrase_invalid));
- passphrase.requestFocus();
- }
- } else {
- passphraseAgain.setError(getString(R.string.missing_passphrase));
- passphraseAgain.requestFocus();
- }
- } else {
- passphrase.setError(getString(R.string.missing_passphrase));
- passphrase.requestFocus();
- }
- }
- //check for right passphrase
- if (selectedAction.equals(AUTHENTICATION)) {
- if (pw.equals(PassphraseWizardActivity.passphrase)) {
- Toast.makeText(this, getString(R.string.unlocked), Toast.LENGTH_SHORT).show();
- this.finish();
- } else {
- passphrase.setError(getString(R.string.passphrase_invalid));
- passphrase.requestFocus();
- }
- }
- }
-
- public void NFC(View view) {
- if (adapter != null) {
- if (getActionBar() != null) {
- getActionBar().setTitle(R.string.nfc_title);
- }
- NFCFragment nfc = new NFCFragment();
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- transaction.replace(R.id.fragmentContainer, nfc).addToBackStack(null).commit();
-
- //if you want to create a new method or just authenticate
- if (CREATE_METHOD.equals(selectedAction)) {
- writeNFC = true;
- } else if (AUTHENTICATION.equals(selectedAction)) {
- readNFC = true;
- }
-
- if (!adapter.isEnabled()) {
- showAlertDialog(getString(R.string.enable_nfc), true);
- }
- } else {
- showAlertDialog(getString(R.string.no_nfc_support), false);
- }
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
- myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
-
- if (writeNFC && CREATE_METHOD.equals(selectedAction)) {
- //write new password on NFC tag
- try {
- if (myTag != null) {
- write(myTag);
- writeNFC = false; //just write once
- Toast.makeText(this, R.string.nfc_write_succesful, Toast.LENGTH_SHORT).show();
- //advance to lockpattern
-// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
-// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
-// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
- }
- } catch (IOException | FormatException e) {
- e.printStackTrace();
- }
-
- } else if (readNFC && AUTHENTICATION.equals(selectedAction)) {
- //read pw from NFC tag
- try {
- if (myTag != null) {
- //if tag detected, read tag
- String pwtag = read(myTag);
- if (output != null && pwtag.equals(output.toString())) {
-
- //passwort matches, go to next view
- Toast.makeText(this, R.string.passphrases_match + "!", Toast.LENGTH_SHORT).show();
-
-// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
-// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
-// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
- readNFC = false; //just once
- } else {
- //passwort doesnt match
- TextView nfc = (TextView) findViewById(R.id.nfcText);
- nfc.setText(R.string.nfc_wrong_tag);
- }
- }
- } catch (IOException | FormatException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
- private void write(Tag tag) throws IOException, FormatException {
- //generate new random key and write them on the tag
- SecureRandom sr = new SecureRandom();
- sr.nextBytes(output);
- NdefRecord[] records = {createRecord(output.toString())};
- NdefMessage message = new NdefMessage(records);
- Ndef ndef = Ndef.get(tag);
- ndef.connect();
- ndef.writeNdefMessage(message);
- ndef.close();
- }
-
- private String read(Tag tag) throws IOException, FormatException {
- //read string from tag
- String password = null;
- Ndef ndef = Ndef.get(tag);
- ndef.connect();
- NdefMessage ndefMessage = ndef.getCachedNdefMessage();
-
- NdefRecord[] records = ndefMessage.getRecords();
- for (NdefRecord ndefRecord : records) {
- if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
- try {
- password = readText(ndefRecord);
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- }
- }
- ndef.close();
- return password;
- }
-
- private String readText(NdefRecord record) throws UnsupportedEncodingException {
- //low-level method for reading nfc
- byte[] payload = record.getPayload();
- String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
- int languageCodeLength = payload[0] & 0063;
- return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
- }
-
- private NdefRecord createRecord(String text) throws UnsupportedEncodingException {
- //low-level method for writing nfc
- String lang = "en";
- byte[] textBytes = text.getBytes();
- byte[] langBytes = lang.getBytes("US-ASCII");
- int langLength = langBytes.length;
- int textLength = textBytes.length;
- byte[] payload = new byte[1 + langLength + textLength];
-
- // set status byte (see NDEF spec for actual bits)
- payload[0] = (byte) langLength;
- // copy langbytes and textbytes into payload
- System.arraycopy(langBytes, 0, payload, 1, langLength);
- System.arraycopy(textBytes, 0, payload, 1 + langLength, textLength);
- return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payload);
- }
-
- public void showAlertDialog(String message, boolean nfc) {
- //This method shows an AlertDialog
- AlertDialog.Builder alert = new AlertDialog.Builder(this);
- alert.setTitle("Information").setMessage(message).setPositiveButton("Ok",
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int i) {
- }
- }
- );
- if (nfc) {
-
- alert.setNeutralButton(R.string.nfc_settings,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialogInterface, int i) {
- startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
- }
- }
- );
- }
- alert.show();
- }
-
- @Override
- public void onPause() {
- //pause this app and free nfc intent
- super.onPause();
- if (adapter != null) {
- WriteModeOff();
- }
- }
-
- @Override
- public void onResume() {
- //resume this app and get nfc intent
- super.onResume();
- if (adapter != null) {
- WriteModeOn();
- }
- }
-
- private void WriteModeOn() {
- //enable nfc for this view
- writeMode = true;
- adapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null);
- }
-
- private void WriteModeOff() {
- //disable nfc for this view
- writeMode = false;
- adapter.disableForegroundDispatch(this);
- }
-
- public static class SelectMethods extends Fragment {
-// private OnFragmentInteractionListener mListener;
-
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- */
- public SelectMethods() {
-
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (getActivity().getActionBar() != null) {
- getActivity().getActionBar().setTitle(R.string.unlock_method);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- // Inflate the layout for this fragment
- return inflater.inflate(R.layout.passphrase_wizard_fragment_select_methods, container, false);
- }
-
-// @Override
-// public void onAttach(Activity activity) {
-// super.onAttach(activity);
-// try {
-// mListener = (OnFragmentInteractionListener) activity;
-// } catch (ClassCastException e) {
-// throw new ClassCastException(activity.toString()
-// + " must implement OnFragmentInteractionListener");
-// }
-// }
-//
-// @Override
-// public void onDetach() {
-// super.onDetach();
-// mListener = null;
-// }
-
- /**
- * This interface must be implemented by activities that contain this
- * fragment to allow an interaction in this fragment to be communicated
- * to the activity and potentially other fragments contained in that
- * activity.
- * <p/>
- * See the Android Training lesson <a href=
- * "http://developer.android.com/training/basics/fragments/communicating.html"
- * >Communicating with Other Fragments</a> for more information.
- */
-// public static interface OnFragmentInteractionListener {
-// public void onFragmentInteraction(Uri uri);
-// }
-
- }
-
-
- // /**
-// * A simple {@link android.support.v4.app.Fragment} subclass.
-// * Activities that contain this fragment must implement the
-// * {@link com.haibison.android.lockpattern.Passphrase.OnFragmentInteractionListener} interface
-// * to handle interaction events.
-// */
- public static class Passphrase extends Fragment {
-
-// private OnFragmentInteractionListener mListener;
-
- public Passphrase() {
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- // Inflate the layout for this fragment
- View view = inflater.inflate(R.layout.passphrase_wizard_fragment_passphrase, container, false);
- EditText passphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain);
- TextView passphraseText = (TextView) view.findViewById(R.id.passphraseText);
- TextView passphraseTextAgain = (TextView) view.findViewById(R.id.passphraseTextAgain);
- String selectedAction = getActivity().getIntent().getAction();
- if (selectedAction.equals(AUTHENTICATION)) {
- passphraseAgain.setVisibility(View.GONE);
- passphraseTextAgain.setVisibility(View.GONE);
- passphraseText.setText(R.string.enter_passphrase);
-// getActivity().getActionBar().setTitle(R.string.enter_passphrase);
- } else if (selectedAction.equals(CREATE_METHOD)) {
- passphraseAgain.setVisibility(View.VISIBLE);
- passphraseTextAgain.setVisibility(View.VISIBLE);
- passphraseText.setText(R.string.passphrase);
-// getActivity().getActionBar().setTitle(R.string.set_passphrase);
- }
- return view;
- }
-
-// @Override
-// public void onAttach(Activity activity) {
-// super.onAttach(activity);
-// try {
-// mListener = (OnFragmentInteractionListener) activity;
-// } catch (ClassCastException e) {
-// throw new ClassCastException(activity.toString()
-// + " must implement OnFragmentInteractionListener");
-// }
-// }
-//
-// @Override
-// public void onDetach() {
-// super.onDetach();
-// mListener = null;
-// }
-
-// /**
-// * This interface must be implemented by activities that contain this
-// * fragment to allow an interaction in this fragment to be communicated
-// * to the activity and potentially other fragments contained in that
-// * activity.
-// * <p/>
-// * See the Android Training lesson <a href=
-// * "http://developer.android.com/training/basics/fragments/communicating.html"
-// * >Communicating with Other Fragments</a> for more information.
-// */
-// public interface OnFragmentInteractionListener {
-// public void onFragmentInteraction(Uri uri);
-// }
- }
-
-
- /**
- * A simple {@link android.support.v4.app.Fragment} subclass.
- * Activities that contain this fragment must implement the
- * interface
- * to handle interaction events.
- * Use the method to
- * create an instance of this fragment.
- */
- public static class NFCFragment extends Fragment {
- // TODO: Rename parameter arguments, choose names that match
- // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
- private static final String ARG_PARAM1 = "param1";
- private static final String ARG_PARAM2 = "param2";
-
- // TODO: Rename and change types of parameters
- private String mParam1;
- private String mParam2;
-
-// private OnFragmentInteractionListener mListener;
-
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment SelectMethods.
- */
- // TODO: Rename and change types and number of parameters
- public static NFCFragment newInstance(String param1, String param2) {
- NFCFragment fragment = new NFCFragment();
- Bundle args = new Bundle();
- args.putString(ARG_PARAM1, param1);
- args.putString(ARG_PARAM2, param2);
- fragment.setArguments(args);
- return fragment;
- }
-
- public NFCFragment() {
- // Required empty public constructor
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (getArguments() != null) {
- mParam1 = getArguments().getString(ARG_PARAM1);
- mParam2 = getArguments().getString(ARG_PARAM2);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- // Inflate the layout for this fragment
- return inflater.inflate(R.layout.passphrase_wizard_fragment_nfc, container, false);
- }
-
-// // TODO: Rename method, update argument and hook method into UI event
-// public void onButtonPressed(Uri uri) {
-// if (mListener != null) {
-// mListener.onFragmentInteraction(uri);
-// }
-// }
-
-// @Override
-// public void onAttach(Activity activity) {
-// super.onAttach(activity);
-// try {
-// mListener = (OnFragmentInteractionListener) activity;
-// } catch (ClassCastException e) {
-// throw new ClassCastException(activity.toString()
-// + " must implement OnFragmentInteractionListener");
-// }
-// }
-
-
-// @Override
-// public void onDetach() {
-// super.onDetach();
-// mListener = null;
-// }
- }
-
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java
index d4858ee5d..e54852f1b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java
@@ -85,11 +85,12 @@ public class QrCodeViewActivity extends BaseActivity {
ActivityCompat.finishAfterTransition(QrCodeViewActivity.this);
}
- String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
- String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
-
+ Uri uri = new Uri.Builder()
+ .scheme(Constants.FINGERPRINT_SCHEME)
+ .opaquePart(KeyFormattingUtils.convertFingerprintToHex(blob))
+ .build();
// create a minimal size qr code, we can keep this in ram no problem
- final Bitmap qrCode = QrCodeUtils.getQRCodeBitmap(qrCodeContent, 0);
+ final Bitmap qrCode = QrCodeUtils.getQRCodeBitmap(uri, 0);
mQrCode.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
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 aa3c36d11..534dbfd05 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java
@@ -18,15 +18,11 @@
package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
-import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
-import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.NumberPicker;
@@ -38,10 +34,10 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+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;
@@ -53,7 +49,8 @@ import edu.cmu.cylab.starslinger.exchange.ExchangeActivity;
import edu.cmu.cylab.starslinger.exchange.ExchangeConfig;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
-public class SafeSlingerActivity extends BaseActivity {
+public class SafeSlingerActivity extends BaseActivity
+ implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
private static final int REQUEST_CODE_SAFE_SLINGER = 211;
@@ -62,6 +59,12 @@ public class SafeSlingerActivity extends BaseActivity {
private long mMasterKeyId;
private int mSelectedNumber = 2;
+ // for CryptoOperationHelper
+ private ArrayList<ParcelableKeyRing> mKeyList;
+ private String mKeyserver;
+ private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper;
+
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -79,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);
@@ -117,69 +120,17 @@ public class SafeSlingerActivity extends BaseActivity {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mOperationHelper != null) {
+ mOperationHelper.handleActivityResult(requestCode, resultCode, data);
+ }
+
if (requestCode == REQUEST_CODE_SAFE_SLINGER) {
if (resultCode == ExchangeActivity.RESULT_EXCHANGE_CANCELED) {
return;
}
- final FragmentActivity activity = SafeSlingerActivity.this;
-
- // Message is received after importing is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- activity,
- getString(R.string.progress_importing),
- ProgressDialog.STYLE_HORIZONTAL,
- true,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
- if (returnData == null) {
- return;
- }
- final ImportKeyResult result =
- returnData.getParcelable(OperationResult.EXTRA_RESULT);
- if (result == null) {
- Log.e(Constants.TAG, "result == null");
- return;
- }
-
- if (!result.success()) {
-// result.createNotify(activity).show();
- // only return if no success...
- Intent data = new Intent();
- data.putExtras(returnData);
- setResult(RESULT_OK, data);
- finish();
- return;
- }
-
-// if (mExchangeMasterKeyId == null) {
-// return;
-// }
-
- Intent certifyIntent = new Intent(activity, CertifyKeyActivity.class);
- certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result);
- certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds());
- certifyIntent.putExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, mMasterKeyId);
- startActivityForResult(certifyIntent, 0);
-
-// mExchangeMasterKeyId = null;
- }
- }
- };
-
Log.d(Constants.TAG, "importKeys started");
- // Send all information needed to service to import key in other thread
- Intent intent = new Intent(activity, KeychainIntentService.class);
-
- intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
-
// instead of giving the entries by Intent extra, cache them into a
// file to prevent Java Binder problems on heavy imports
// read FileImportCache for more info.
@@ -190,25 +141,18 @@ public class SafeSlingerActivity extends BaseActivity {
// We parcel this iteratively into a file - anything we can
// display here, we should be able to import.
ParcelableFileCache<ParcelableKeyRing> cache =
- new ParcelableFileCache<>(activity, "key_import.pcl");
+ new ParcelableFileCache<>(this, "key_import.pcl");
cache.writeCache(it.size(), it.iterator());
- // fill values for this action
- Bundle bundle = new Bundle();
- intent.putExtra(KeychainIntentService.EXTRA_DATA, bundle);
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- saveHandler.showProgressDialog(activity);
+ mOperationHelper =
+ new CryptoOperationHelper(1, this, this, R.string.progress_importing);
- // start service with intent
- activity.startService(intent);
+ mKeyList = null;
+ mKeyserver = null;
+ mOperationHelper.cryptoOperation();
} catch (IOException e) {
Log.e(Constants.TAG, "Problem writing cache file", e);
- Notify.create(activity, "Problem writing cache file!", Notify.Style.ERROR).show();
+ Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR).show();
}
} else {
// give everything else down to KeyListActivity!
@@ -235,4 +179,39 @@ public class SafeSlingerActivity extends BaseActivity {
return list;
}
+ // CryptoOperationHelper.Callback functions
+
+ @Override
+ public ImportKeyringParcel createOperationInput() {
+ return new ImportKeyringParcel(mKeyList, mKeyserver);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(ImportKeyResult result) {
+ Intent certifyIntent = new Intent(this, CertifyKeyActivity.class);
+ certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result);
+ certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds());
+ certifyIntent.putExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, mMasterKeyId);
+ startActivityForResult(certifyIntent, 0);
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+
+ }
+
+ @Override
+ public void onCryptoOperationError(ImportKeyResult result) {
+ Bundle returnData = new Bundle();
+ returnData.putParcelable(OperationResult.EXTRA_RESULT, result);
+ Intent data = new Intent();
+ data.putExtras(returnData);
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
}
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 442bdf8f7..4077f1c84 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* This program is free software: you can redistribute it and/or modify
@@ -17,42 +18,59 @@
package org.sufficientlysecure.keychain.ui;
-import android.annotation.TargetApi;
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Activity;
+import android.content.ContentResolver;
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.preference.SwitchPreference;
+import android.provider.ContactsContract;
import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
-import org.spongycastle.bcpg.CompressionAlgorithmTags;
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;
-public class SettingsActivity extends PreferenceActivity {
+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.setThemes(R.style.Theme_Keychain_Light, R.style.Theme_Keychain_Dark);
+ mThemeChanger.changeTheme();
super.onCreate(savedInstanceState);
setupToolbar();
@@ -76,14 +94,14 @@ public class SettingsActivity extends PreferenceActivity {
}
});
initializeSearchKeyserver(
- (CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
+ (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
);
initializeSearchKeybase(
- (CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
+ (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
);
} else if (action != null && action.equals(ACTION_PREFS_ADV)) {
- addPreferencesFromResource(R.xml.adv_preferences);
+ addPreferencesFromResource(R.xml.passphrase_preferences);
initializePassphraseCacheSubs(
(CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS));
@@ -91,28 +109,29 @@ public class SettingsActivity extends PreferenceActivity {
initializePassphraseCacheTtl(
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
- int[] valueIds = new int[]{
- CompressionAlgorithmTags.UNCOMPRESSED,
- CompressionAlgorithmTags.ZIP,
- CompressionAlgorithmTags.ZLIB,
- CompressionAlgorithmTags.BZIP2,
- };
- String[] entries = new String[]{
- getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")",
- "ZIP (" + getString(R.string.compression_fast) + ")",
- "ZLIB (" + getString(R.string.compression_fast) + ")",
- "BZIP2 (" + getString(R.string.compression_very_slow) + ")",};
- String[] values = new String[valueIds.length];
- for (int i = 0; i < values.length; ++i) {
- values[i] = "" + valueIds[i];
- }
-
initializeUseDefaultYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
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();
+ overridePendingTransition(0, 0);
+ startActivity(intent);
+ overridePendingTransition(0, 0);
}
}
@@ -122,13 +141,14 @@ public class SettingsActivity extends PreferenceActivity {
private void setupToolbar() {
ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
LinearLayout content = (LinearLayout) root.getChildAt(0);
- LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.preference_toolbar_activity, null);
+ LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.preference_toolbar, null);
root.removeAllViews();
toolbarContainer.addView(content);
root.addView(toolbarContainer);
Toolbar toolbar = (Toolbar) toolbarContainer.findViewById(R.id.toolbar);
+
toolbar.setTitle(R.string.title_preferences);
toolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp));
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@@ -141,34 +161,13 @@ public class SettingsActivity extends PreferenceActivity {
}
@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);
}
/**
- * This fragment shows the Cloud Search preferences in android 3.0+
+ * This fragment shows the Cloud Search preferences
*/
public static class CloudSearchPrefsFragment extends PreferenceFragment {
@@ -196,10 +195,10 @@ public class SettingsActivity extends PreferenceActivity {
}
});
initializeSearchKeyserver(
- (CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
+ (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
);
initializeSearchKeybase(
- (CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
+ (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
);
}
@@ -207,12 +206,7 @@ public class SettingsActivity extends PreferenceActivity {
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;
}
@@ -226,16 +220,16 @@ public class SettingsActivity extends PreferenceActivity {
}
/**
- * This fragment shows the advanced preferences in android 3.0+
+ * This fragment shows the PIN/password preferences
*/
- public static class AdvancedPrefsFragment extends PreferenceFragment {
+ public static class PassphrasePrefsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
- addPreferencesFromResource(R.xml.adv_preferences);
+ addPreferencesFromResource(R.xml.passphrase_preferences);
initializePassphraseCacheSubs(
(CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS));
@@ -243,25 +237,6 @@ public class SettingsActivity extends PreferenceActivity {
initializePassphraseCacheTtl(
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
- int[] valueIds = new int[]{
- CompressionAlgorithmTags.UNCOMPRESSED,
- CompressionAlgorithmTags.ZIP,
- CompressionAlgorithmTags.ZLIB,
- CompressionAlgorithmTags.BZIP2,
- };
-
- String[] entries = new String[]{
- getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")",
- "ZIP (" + getString(R.string.compression_fast) + ")",
- "ZLIB (" + getString(R.string.compression_fast) + ")",
- "BZIP2 (" + getString(R.string.compression_very_slow) + ")",
- };
-
- String[] values = new String[valueIds.length];
- for (int i = 0; i < values.length; ++i) {
- values[i] = "" + valueIds[i];
- }
-
initializeUseDefaultYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
@@ -270,10 +245,346 @@ public class SettingsActivity extends PreferenceActivity {
}
}
- @TargetApi(Build.VERSION_CODES.KITKAT)
+ public static class ProxyPrefsFragment extends PreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ new Initializer(this).initialize();
+
+ }
+
+ public static class Initializer {
+ private SwitchPreference mUseTor;
+ private SwitchPreference 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 = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY);
+ mUseNormalProxy = (SwitchPreference) 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));
+ }
+ }
+
+ /**
+ * This fragment shows the keyserver/contacts sync preferences
+ */
+ public static class SyncPrefsFragment extends PreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.sync_preferences);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ // this needs to be done in onResume since the user can change sync values from Android
+ // settings and we need to reflect that change when the user navigates back
+ AccountManager manager = AccountManager.get(getActivity());
+ final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0];
+ // for keyserver sync
+ initializeSyncCheckBox(
+ (SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER),
+ account,
+ Constants.PROVIDER_AUTHORITY
+ );
+ // for contacts sync
+ initializeSyncCheckBox(
+ (SwitchPreference) findPreference(Constants.Pref.SYNC_CONTACTS),
+ account,
+ ContactsContract.AUTHORITY
+ );
+ }
+
+ private void initializeSyncCheckBox(final SwitchPreference syncCheckBox,
+ final Account account,
+ final String authority) {
+ boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority);
+ syncCheckBox.setChecked(syncEnabled);
+ setSummary(syncCheckBox, authority, syncEnabled);
+
+ syncCheckBox.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ boolean syncEnabled = (Boolean) newValue;
+ if (syncEnabled) {
+ ContentResolver.setSyncAutomatically(account, authority, true);
+ } else {
+ // disable syncs
+ ContentResolver.setSyncAutomatically(account, authority, false);
+ // cancel any ongoing/pending syncs
+ ContentResolver.cancelSync(account, authority);
+ }
+ setSummary(syncCheckBox, authority, syncEnabled);
+ return true;
+ }
+ });
+ }
+
+ private void setSummary(SwitchPreference syncCheckBox, String authority,
+ boolean checked) {
+ switch (authority) {
+ case Constants.PROVIDER_AUTHORITY: {
+ if (checked) {
+ syncCheckBox.setSummary(R.string.label_sync_settings_keyserver_summary_on);
+ } else {
+ syncCheckBox.setSummary(R.string.label_sync_settings_keyserver_summary_off);
+ }
+ break;
+ }
+ case ContactsContract.AUTHORITY: {
+ if (checked) {
+ syncCheckBox.setSummary(R.string.label_sync_settings_contacts_summary_on);
+ } else {
+ syncCheckBox.setSummary(R.string.label_sync_settings_contacts_summary_off);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * This fragment shows experimental features
+ */
+ public static class ExperimentalPrefsFragment extends PreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.experimental_preferences);
+
+ initializeExperimentalEnableWordConfirm(
+ (SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_WORD_CONFIRM));
+
+ initializeExperimentalEnableLinkedIdentities(
+ (SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES));
+
+ initializeExperimentalEnableKeybase(
+ (SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_KEYBASE));
+
+ initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
+
+ }
+ }
+
protected boolean isValidFragment(String fragmentName) {
- return AdvancedPrefsFragment.class.getName().equals(fragmentName)
+ return PassphrasePrefsFragment.class.getName().equals(fragmentName)
|| CloudSearchPrefsFragment.class.getName().equals(fragmentName)
+ || ProxyPrefsFragment.class.getName().equals(fragmentName)
+ || GuiPrefsFragment.class.getName().equals(fragmentName)
+ || SyncPrefsFragment.class.getName().equals(fragmentName)
+ || ExperimentalPrefsFragment.class.getName().equals(fragmentName)
|| super.isValidFragment(fragmentName);
}
@@ -302,7 +613,23 @@ public class SettingsActivity extends PreferenceActivity {
});
}
- private static void initializeSearchKeyserver(final CheckBoxPreference mSearchKeyserver) {
+ 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);
+
+ ((SettingsActivity) mTheme.getContext()).recreate();
+
+ return false;
+ }
+ });
+ }
+
+ private static void initializeSearchKeyserver(final SwitchPreference mSearchKeyserver) {
Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs();
mSearchKeyserver.setChecked(prefs.searchKeyserver);
mSearchKeyserver.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@@ -315,7 +642,7 @@ public class SettingsActivity extends PreferenceActivity {
});
}
- private static void initializeSearchKeybase(final CheckBoxPreference mSearchKeybase) {
+ private static void initializeSearchKeybase(final SwitchPreference mSearchKeybase) {
Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs();
mSearchKeybase.setChecked(prefs.searchKeybase);
mSearchKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@@ -332,7 +659,8 @@ public class SettingsActivity extends PreferenceActivity {
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) {
@@ -356,4 +684,37 @@ public class SettingsActivity extends PreferenceActivity {
}
});
}
+
+ private static void initializeExperimentalEnableWordConfirm(final SwitchPreference mExperimentalEnableWordConfirm) {
+ mExperimentalEnableWordConfirm.setChecked(sPreferences.getExperimentalEnableWordConfirm());
+ mExperimentalEnableWordConfirm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mExperimentalEnableWordConfirm.setChecked((Boolean) newValue);
+ sPreferences.setExperimentalEnableWordConfirm((Boolean) newValue);
+ return false;
+ }
+ });
+ }
+
+ private static void initializeExperimentalEnableLinkedIdentities(final SwitchPreference mExperimentalEnableLinkedIdentities) {
+ mExperimentalEnableLinkedIdentities.setChecked(sPreferences.getExperimentalEnableLinkedIdentities());
+ mExperimentalEnableLinkedIdentities.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mExperimentalEnableLinkedIdentities.setChecked((Boolean) newValue);
+ sPreferences.setExperimentalEnableLinkedIdentities((Boolean) newValue);
+ return false;
+ }
+ });
+ }
+
+ private static void initializeExperimentalEnableKeybase(final SwitchPreference mExperimentalKeybase) {
+ mExperimentalKeybase.setChecked(sPreferences.getExperimentalEnableKeybase());
+ mExperimentalKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mExperimentalKeybase.setChecked((Boolean) newValue);
+ sPreferences.setExperimentalEnableKeybase((Boolean) newValue);
+ return false;
+ }
+ });
+ }
}
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 8f025c769..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, R.string.add_keyserver_dialog_title);
- 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..d8edbe4f8
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
@@ -0,0 +1,360 @@
+/*
+ * 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 {
+
+ 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);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ 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 5c8e6bb5d..0415128a2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
@@ -17,40 +17,42 @@
package org.sufficientlysecure.keychain.ui;
-import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
import android.support.v4.app.NavUtils;
import android.view.MenuItem;
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;
+import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
/**
* Sends the selected public key to a keyserver
*/
-public class UploadKeyActivity extends BaseActivity {
+public class UploadKeyActivity extends BaseActivity
+ implements CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> {
private View mUploadButton;
private Spinner mKeyServerSpinner;
private Uri mDataUri;
+ // CryptoOperationHelper.Callback vars
+ private String mKeyserver;
+ private Uri mUnifiedKeyringUri;
+ private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -90,52 +92,23 @@ public class UploadKeyActivity extends BaseActivity {
setContentView(R.layout.upload_key_activity);
}
- private void uploadKey() {
- // Send all information needed to service to upload key in other thread
- Intent intent = new Intent(this, KeychainIntentService.class);
-
- intent.setAction(KeychainIntentService.ACTION_UPLOAD_KEYRING);
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mUploadOpHelper != null) {
+ mUploadOpHelper.handleActivityResult(requestCode, resultCode, data);
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
- // set data uri as path to keyring
+ private void uploadKey() {
Uri blobUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
- intent.setData(blobUri);
-
- // fill values for this action
- Bundle data = new Bundle();
+ mUnifiedKeyringUri = blobUri;
String server = (String) mKeyServerSpinner.getSelectedItem();
- data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, server);
-
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Message is received after uploading is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- this,
- getString(R.string.progress_uploading),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
-
- Toast.makeText(UploadKeyActivity.this, R.string.msg_crt_upload_success,
- Toast.LENGTH_SHORT).show();
- finish();
- }
- }
- };
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- saveHandler.showProgressDialog(this);
+ mKeyserver = server;
- // start service with intent
- startService(intent);
+ mUploadOpHelper = new CryptoOperationHelper(1, this, this, R.string.progress_uploading);
+ mUploadOpHelper.cryptoOperation();
}
@Override
@@ -150,4 +123,29 @@ public class UploadKeyActivity extends BaseActivity {
}
return super.onOptionsItemSelected(item);
}
+
+ @Override
+ public ExportKeyringParcel createOperationInput() {
+ return new ExportKeyringParcel(mKeyserver, mUnifiedKeyringUri);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(ExportResult result) {
+ result.createNotify(this).show();
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+
+ }
+
+ @Override
+ public void onCryptoOperationError(ExportResult result) {
+ result.createNotify(this).show();
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
}
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 7ae4f1be3..fd50ed5ef 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
@@ -31,9 +31,10 @@ 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.design.widget.AppBarLayout;
+import android.support.design.widget.CollapsingToolbarLayout;
+import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager;
@@ -47,16 +48,17 @@ import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
+import android.widget.FrameLayout;
import android.widget.ImageButton;
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;
@@ -65,13 +67,10 @@ import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
-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.dialog.DeleteKeyDialogFragment;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
@@ -85,37 +84,45 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.NfcHelper;
import org.sufficientlysecure.keychain.util.Preferences;
+import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.HashMap;
public class ViewKeyActivity extends BaseNfcActivity implements
- LoaderManager.LoaderCallbacks<Cursor> {
+ LoaderManager.LoaderCallbacks<Cursor>,
+ CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
public static final String EXTRA_NFC_AID = "nfc_aid";
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;
- private TextView mName;
+ // For CryptoOperationHelper.Callback
+ private String mKeyserver;
+ private ArrayList<ParcelableKeyRing> mKeyList;
+ private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper;
+
private TextView mStatusText;
private ImageView mStatusImage;
- private RelativeLayout mBigToolbar;
+ private AppBarLayout mAppBarLayout;
+ private CollapsingToolbarLayout mCollapsingToolbarLayout;
private ImageButton mActionEncryptFile;
private ImageButton mActionEncryptText;
private ImageButton mActionNfc;
private FloatingActionButton mFab;
private ImageView mPhoto;
+ private FrameLayout mPhotoLayout;
private ImageView mQrCode;
private CardView mQrCodeLayout;
@@ -132,6 +139,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements
private boolean mIsRevoked = false;
private boolean mIsExpired = false;
+ private boolean mShowYubikeyAfterCreation = false;
+
private MenuItem mRefreshItem;
private boolean mIsRefreshing;
private Animation mRotate, mRotateSpin;
@@ -139,26 +148,31 @@ 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);
- mName = (TextView) findViewById(R.id.view_key_name);
mStatusText = (TextView) findViewById(R.id.view_key_status);
mStatusImage = (ImageView) findViewById(R.id.view_key_status_image);
- mBigToolbar = (RelativeLayout) findViewById(R.id.toolbar_big);
+ mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar_layout);
+ mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
mActionEncryptFile = (ImageButton) findViewById(R.id.view_key_action_encrypt_files);
mActionEncryptText = (ImageButton) findViewById(R.id.view_key_action_encrypt_text);
mActionNfc = (ImageButton) findViewById(R.id.view_key_action_nfc);
mFab = (FloatingActionButton) findViewById(R.id.fab);
mPhoto = (ImageView) findViewById(R.id.view_key_photo);
+ mPhotoLayout = (FrameLayout) findViewById(R.id.view_key_photo_layout);
mQrCode = (ImageView) findViewById(R.id.view_key_qr_code);
mQrCodeLayout = (CardView) findViewById(R.id.view_key_qr_code_layout);
@@ -287,14 +301,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements
.replace(R.id.view_key_fragment, frag)
.commit();
- if (getIntent().hasExtra(EXTRA_NFC_AID)) {
- Intent intent = getIntent();
- byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
- String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
- byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
- showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid);
+ if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) {
+ final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(mDataUri);
+ manager.beginTransaction()
+ .replace(R.id.view_key_keybase_fragment, keybaseFrag)
+ .commit();
}
+ // need to postpone loading of the yubikey fragment until after mMasterKeyId
+ // is available, but we mark here that this should be done
+ mShowYubikeyAfterCreation = true;
+
}
@Override
@@ -320,31 +337,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: {
@@ -372,7 +369,11 @@ public class ViewKeyActivity extends BaseNfcActivity implements
return true;
}
case R.id.menu_key_view_certify_fingerprint: {
- certifyFingeprint(mDataUri);
+ certifyFingeprint(mDataUri, false);
+ return true;
+ }
+ case R.id.menu_key_view_certify_fingerprint_word: {
+ certifyFingeprint(mDataUri, true);
return true;
}
}
@@ -384,11 +385,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements
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 addLinked = menu.findItem(R.id.menu_key_view_add_linked_identity);
addLinked.setVisible(mIsSecret);
MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint);
certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked);
+ MenuItem certifyFingerprintWord = menu.findItem(R.id.menu_key_view_certify_fingerprint_word);
+ certifyFingerprintWord.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked
+ && Preferences.getPreferences(this).getExperimentalEnableWordConfirm());
return true;
}
@@ -400,40 +407,19 @@ public class ViewKeyActivity extends BaseNfcActivity implements
startActivityForResult(scanQrCode, REQUEST_QR_FINGERPRINT);
}
- private void certifyFingeprint(Uri dataUri) {
+ private void certifyFingeprint(Uri dataUri, boolean enableWordConfirm) {
Intent intent = new Intent(this, CertifyFingerprintActivity.class);
intent.setData(dataUri);
+ intent.putExtra(CertifyFingerprintActivity.EXTRA_ENABLE_WORD_CONFIRM, enableWordConfirm);
- 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});
+ intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId});
- startCertifyIntent(intent);
- }
-
- private void startCertifyIntent(Intent intent) {
- // Message is received after signing is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) {
- 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(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- startActivityForResult(intent, 0);
+ startActivityForResult(intent, REQUEST_CERTIFY);
}
private void showQrCodeDialog() {
@@ -458,95 +444,108 @@ 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, new File(Constants.Path.APP_DIR,
+ KeyFormattingUtils.convertKeyIdToHex(mMasterKeyId) + ".sec.asc"), 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 (requestCode == REQUEST_QR_FINGERPRINT && resultCode == Activity.RESULT_OK) {
+ if (mOperationHelper.handleActivityResult(requestCode, resultCode, data)) {
+ return;
+ }
- // 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();
+ switch (requestCode) {
+ case REQUEST_QR_FINGERPRINT: {
+
+ if (resultCode != Activity.RESULT_OK) {
+ return;
+ }
+
+ // 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: {
+ if (resultCode != Activity.RESULT_OK) {
+ return;
+ }
+
+ backupToFile();
return;
}
- if (mFingerprint.equalsIgnoreCase(fp)) {
- certifyImmediate();
- } else {
- Notify.create(this, "Fingerprints did not match!",
- Notify.LENGTH_LONG, Notify.Style.ERROR).show();
+
+ case REQUEST_CERTIFY: {
+ if (resultCode != Activity.RESULT_OK) {
+ return;
+ }
+
+ if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
+ OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
+ result.createNotify(this).show();
+ }
+ return;
}
- return;
- }
+ case REQUEST_DELETE: {
+ if (resultCode != Activity.RESULT_OK) {
+ return;
+ }
- if (requestCode == REQUEST_DELETE && resultCode == Activity.RESULT_OK) {
- deleteKey();
+ 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 {
- final byte[] nfcFingerprints = nfcGetFingerprints();
- final String nfcUserId = nfcGetUserId();
- final byte[] nfcAid = nfcGetAid();
+ mNfcFingerprints = nfcGetFingerprints();
+ mNfcUserId = nfcGetUserId();
+ mNfcAid = nfcGetAid();
+ }
+
+ @Override
+ protected void onNfcPostExecute() throws IOException {
- long yubiKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints);
+ long yubiKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
try {
@@ -557,7 +556,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,14 +569,13 @@ 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();
}
}, R.string.snack_yubikey_view).show();
-
// and if it's not found, offer import
} catch (PgpKeyNotFoundException e) {
Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG,
@@ -586,28 +584,36 @@ 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(byte[] nfcFingerprints, String nfcUserId, byte[] nfcAid) {
- ViewKeyYubiKeyFragment frag = ViewKeyYubiKeyFragment.newInstance(
- nfcFingerprints, nfcUserId, nfcAid);
+ public void showYubiKeyFragment(
+ final byte[] nfcFingerprints, final String nfcUserId, final byte[] nfcAid) {
- FragmentManager manager = getSupportFragmentManager();
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ ViewKeyYubiKeyFragment frag = ViewKeyYubiKeyFragment.newInstance(
+ mMasterKeyId, nfcFingerprints, nfcUserId, nfcAid);
+
+ FragmentManager manager = getSupportFragmentManager();
+
+ manager.popBackStack("yubikey", FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ manager.beginTransaction()
+ .addToBackStack("yubikey")
+ .replace(R.id.view_key_fragment, frag)
+ // if this is called while the activity wasn't resumed, just forget it happened
+ .commitAllowingStateLoss();
+ }
+ });
- manager.popBackStack("yubikey", FragmentManager.POP_BACK_STACK_INCLUSIVE);
- manager.beginTransaction()
- .addToBackStack("yubikey")
- .replace(R.id.view_key_fragment, frag)
- .commit();
}
private void encrypt(Uri dataUri, boolean text) {
@@ -620,7 +626,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);
@@ -638,76 +644,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);
-
- // Message is received after importing is done in KeychainIntentService
- ServiceProgressHandler serviceHandler = new ServiceProgressHandler(this) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
-
- mIsRefreshing = false;
-
- if (returnData == null) {
- finish();
- return;
- }
- final ImportKeyResult result =
- returnData.getParcelable(OperationResult.EXTRA_RESULT);
- result.createNotify(ViewKeyActivity.this).show();
- }
- }
- };
-
- // fill values for this action
- Bundle data = new Bundle();
-
- // search config
- {
- Preferences prefs = Preferences.getPreferences(this);
- Preferences.CloudSearchPrefs cloudPrefs =
- new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
- data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
- }
-
- data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, entries);
-
- // Send all information needed to service to query keys in other thread
- Intent intent = new Intent(this, KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(serviceHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- serviceHandler.showProgressDialog(this);
-
- // start service with intent
- startService(intent);
-
- }
-
private void editKey(Uri dataUri) {
Intent editIntent = new Intent(this, EditKeyActivity.class);
editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
@@ -735,9 +671,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements
AsyncTask<Void, Void, Bitmap> loadTask =
new AsyncTask<Void, Void, Bitmap>() {
protected Bitmap doInBackground(Void... unused) {
- String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
+ Uri uri = new Uri.Builder()
+ .scheme(Constants.FINGERPRINT_SCHEME)
+ .opaquePart(fingerprint)
+ .build();
// render with minimal size
- return QrCodeUtils.getQRCodeBitmap(qrCodeContent, 0);
+ return QrCodeUtils.getQRCodeBitmap(uri, 0);
}
protected void onPostExecute(Bitmap qrCode) {
@@ -761,7 +700,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,
@@ -797,6 +736,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,
@@ -809,172 +767,181 @@ public class ViewKeyActivity extends BaseNfcActivity implements
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
switch (loader.getId()) {
-
case LOADER_ID_UNIFIED: {
- if (!data.moveToFirst()) {
- return;
- }
-
- // get name, email, and comment from USER_ID
- KeyRing.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID));
- if (mainUserId.name != null) {
- mName.setText(mainUserId.name);
- } else {
- mName.setText(R.string.user_id_no_name);
- }
-
- mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
- mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
- data.getBlob(INDEX_FINGERPRINT));
-
- mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
- mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
- mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
- mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
- mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
-
- // if the refresh animation isn't playing
- if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) {
- // re-create options menu based on mIsSecret, mIsVerified
- supportInvalidateOptionsMenu();
- // this is done at the end of the animation otherwise
- }
-
- AsyncTask<Long, Void, Bitmap> photoTask =
- new AsyncTask<Long, Void, Bitmap>() {
- protected Bitmap doInBackground(Long... mMasterKeyId) {
- return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), mMasterKeyId[0], true);
- }
-
- protected void onPostExecute(Bitmap photo) {
- mPhoto.setImageBitmap(photo);
- mPhoto.setVisibility(View.VISIBLE);
- }
- };
-
- // Note: order is important
- int color;
- if (mIsRevoked) {
- mStatusText.setText(R.string.view_key_revoked);
- mStatusImage.setVisibility(View.VISIBLE);
- KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
- State.REVOKED, R.color.icons, true);
- color = getResources().getColor(R.color.android_red_light);
-
- mActionEncryptFile.setVisibility(View.GONE);
- mActionEncryptText.setVisibility(View.GONE);
- mActionNfc.setVisibility(View.GONE);
- mFab.setVisibility(View.GONE);
- mQrCodeLayout.setVisibility(View.GONE);
- } else if (mIsExpired) {
- if (mIsSecret) {
- mStatusText.setText(R.string.view_key_expired_secret);
+ if (data.moveToFirst()) {
+ // get name, email, and comment from USER_ID
+ KeyRing.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID));
+ if (mainUserId.name != null) {
+ mCollapsingToolbarLayout.setTitle(mainUserId.name);
} else {
- mStatusText.setText(R.string.view_key_expired);
- }
- mStatusImage.setVisibility(View.VISIBLE);
- KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
- State.EXPIRED, R.color.icons, true);
- color = getResources().getColor(R.color.android_red_light);
-
- mActionEncryptFile.setVisibility(View.GONE);
- mActionEncryptText.setVisibility(View.GONE);
- mActionNfc.setVisibility(View.GONE);
- mFab.setVisibility(View.GONE);
- mQrCodeLayout.setVisibility(View.GONE);
- } else if (mIsSecret) {
- mStatusText.setText(R.string.view_key_my_key);
- mStatusImage.setVisibility(View.GONE);
- color = getResources().getColor(R.color.primary);
- // reload qr code only if the fingerprint changed
- if (!mFingerprint.equals(mQrCodeLoaded)) {
- loadQrCode(mFingerprint);
- }
- photoTask.execute(mMasterKeyId);
- mQrCodeLayout.setVisibility(View.VISIBLE);
-
- // and place leftOf qr code
- RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams)
- mName.getLayoutParams();
- // remove right margin
- nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- nameParams.setMarginEnd(0);
+ mCollapsingToolbarLayout.setTitle(getString(R.string.user_id_no_name));
}
- nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
- mName.setLayoutParams(nameParams);
-
- RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams)
- mStatusText.getLayoutParams();
- statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- statusParams.setMarginEnd(0);
- }
- statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
- mStatusText.setLayoutParams(statusParams);
- mActionEncryptFile.setVisibility(View.VISIBLE);
- mActionEncryptText.setVisibility(View.VISIBLE);
+ mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
+ mFingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT));
+
+ // if it wasn't shown yet, display yubikey fragment
+ if (mShowYubikeyAfterCreation && getIntent().hasExtra(EXTRA_NFC_AID)) {
+ mShowYubikeyAfterCreation = false;
+ Intent intent = getIntent();
+ byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
+ String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
+ byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
+ showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid);
+ }
- // invokeBeam is available from API 21
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- mActionNfc.setVisibility(View.VISIBLE);
- } else {
- mActionNfc.setVisibility(View.GONE);
+ mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
+ mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
+ mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
+ mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
+ mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
+
+ // if the refresh animation isn't playing
+ if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) {
+ // re-create options menu based on mIsSecret, mIsVerified
+ supportInvalidateOptionsMenu();
+ // this is done at the end of the animation otherwise
}
- mFab.setVisibility(View.VISIBLE);
- mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
- } else {
- mActionEncryptFile.setVisibility(View.VISIBLE);
- mActionEncryptText.setVisibility(View.VISIBLE);
- mQrCodeLayout.setVisibility(View.GONE);
- mActionNfc.setVisibility(View.GONE);
- if (mIsVerified) {
- mStatusText.setText(R.string.view_key_verified);
+ AsyncTask<Long, Void, Bitmap> photoTask =
+ new AsyncTask<Long, Void, Bitmap>() {
+ protected Bitmap doInBackground(Long... mMasterKeyId) {
+ return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(),
+ mMasterKeyId[0], true);
+ }
+
+ protected void onPostExecute(Bitmap photo) {
+ if (photo == null) {
+ return;
+ }
+
+ mPhoto.setImageBitmap(photo);
+ mPhotoLayout.setVisibility(View.VISIBLE);
+ }
+ };
+
+ // Note: order is important
+ int color;
+ if (mIsRevoked) {
+ mStatusText.setText(R.string.view_key_revoked);
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
- State.VERIFIED, R.color.icons, true);
- color = getResources().getColor(R.color.primary);
- photoTask.execute(mMasterKeyId);
+ State.REVOKED, R.color.icons, true);
+ color = getResources().getColor(R.color.key_flag_red);
+ mActionEncryptFile.setVisibility(View.INVISIBLE);
+ mActionEncryptText.setVisibility(View.INVISIBLE);
+ mActionNfc.setVisibility(View.INVISIBLE);
mFab.setVisibility(View.GONE);
- } else {
- mStatusText.setText(R.string.view_key_unverified);
+ mQrCodeLayout.setVisibility(View.GONE);
+ } else if (mIsExpired) {
+ if (mIsSecret) {
+ mStatusText.setText(R.string.view_key_expired_secret);
+ } else {
+ mStatusText.setText(R.string.view_key_expired);
+ }
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
- State.UNVERIFIED, R.color.icons, true);
- color = getResources().getColor(R.color.android_orange_light);
+ State.EXPIRED, R.color.icons, true);
+ color = getResources().getColor(R.color.key_flag_red);
+ mActionEncryptFile.setVisibility(View.INVISIBLE);
+ mActionEncryptText.setVisibility(View.INVISIBLE);
+ mActionNfc.setVisibility(View.INVISIBLE);
+ mFab.setVisibility(View.GONE);
+ mQrCodeLayout.setVisibility(View.GONE);
+ } else if (mIsSecret) {
+ mStatusText.setText(R.string.view_key_my_key);
+ mStatusImage.setVisibility(View.GONE);
+ color = getResources().getColor(R.color.key_flag_green);
+ // reload qr code only if the fingerprint changed
+ if (!mFingerprint.equals(mQrCodeLoaded)) {
+ loadQrCode(mFingerprint);
+ }
+ photoTask.execute(mMasterKeyId);
+ mQrCodeLayout.setVisibility(View.VISIBLE);
+
+ // and place leftOf qr code
+// RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams)
+// mName.getLayoutParams();
+// // remove right margin
+// nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
+// if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+// nameParams.setMarginEnd(0);
+// }
+// nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
+// mName.setLayoutParams(nameParams);
+
+ RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams)
+ mStatusText.getLayoutParams();
+ statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ statusParams.setMarginEnd(0);
+ }
+ statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
+ mStatusText.setLayoutParams(statusParams);
+
+ mActionEncryptFile.setVisibility(View.VISIBLE);
+ mActionEncryptText.setVisibility(View.VISIBLE);
+
+ // invokeBeam is available from API 21
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mActionNfc.setVisibility(View.VISIBLE);
+ } else {
+ mActionNfc.setVisibility(View.GONE);
+ }
mFab.setVisibility(View.VISIBLE);
- }
- }
+ // noinspection deprecation (no getDrawable with theme at current minApi level 15!)
+ mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
+ } else {
+ mActionEncryptFile.setVisibility(View.VISIBLE);
+ mActionEncryptText.setVisibility(View.VISIBLE);
+ mQrCodeLayout.setVisibility(View.GONE);
+ mActionNfc.setVisibility(View.GONE);
- if (mPreviousColor == 0 || mPreviousColor == color) {
- mStatusBar.setBackgroundColor(color);
- mBigToolbar.setBackgroundColor(color);
- mPreviousColor = color;
- } else {
- ObjectAnimator colorFade1 =
- ObjectAnimator.ofObject(mStatusBar, "backgroundColor",
- new ArgbEvaluator(), mPreviousColor, color);
- ObjectAnimator colorFade2 =
- ObjectAnimator.ofObject(mBigToolbar, "backgroundColor",
- new ArgbEvaluator(), mPreviousColor, color);
-
- colorFade1.setDuration(1200);
- colorFade2.setDuration(1200);
- colorFade1.start();
- colorFade2.start();
- mPreviousColor = color;
- }
+ if (mIsVerified) {
+ mStatusText.setText(R.string.view_key_verified);
+ mStatusImage.setVisibility(View.VISIBLE);
+ KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
+ State.VERIFIED, R.color.icons, true);
+ color = getResources().getColor(R.color.key_flag_green);
+ photoTask.execute(mMasterKeyId);
+
+ mFab.setVisibility(View.GONE);
+ } else {
+ mStatusText.setText(R.string.view_key_unverified);
+ mStatusImage.setVisibility(View.VISIBLE);
+ KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
+ State.UNVERIFIED, R.color.icons, true);
+ color = getResources().getColor(R.color.key_flag_orange);
+
+ mFab.setVisibility(View.VISIBLE);
+ }
+ }
- //noinspection deprecation
- mStatusImage.setAlpha(80);
+ if (mPreviousColor == 0 || mPreviousColor == color) {
+ mAppBarLayout.setBackgroundColor(color);
+ mCollapsingToolbarLayout.setContentScrimColor(color);
+ mCollapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color));
+ mPreviousColor = color;
+ } else {
+ ObjectAnimator colorFade =
+ ObjectAnimator.ofObject(mAppBarLayout, "backgroundColor",
+ new ArgbEvaluator(), mPreviousColor, color);
+ mCollapsingToolbarLayout.setContentScrimColor(color);
+ mCollapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color));
+
+ colorFade.setDuration(1200);
+ colorFade.start();
+ mPreviousColor = color;
+ }
- break;
+ //noinspection deprecation
+ mStatusImage.setAlpha(80);
+ break;
+ }
}
}
}
@@ -984,4 +951,64 @@ 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);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(ImportKeyResult result) {
+ mIsRefreshing = false;
+ result.createNotify(this).show();
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+ mIsRefreshing = false;
+ }
+
+ @Override
+ public void onCryptoOperationError(ImportKeyResult result) {
+ mIsRefreshing = false;
+ result.createNotify(this).show();
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return true;
+ }
+
}
+
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 9e8a12c8a..edd9feec9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java
@@ -53,13 +53,14 @@ public class ViewKeyAdvActivity extends BaseActivity implements
protected Uri mDataUri;
public static final String EXTRA_SELECTED_TAB = "selected_tab";
- public static final int TAB_MAIN = 0;
- public static final int TAB_SHARE = 1;
+ public static final int TAB_SHARE = 0;
+ public static final int TAB_IDENTITIES = 1;
+ public static final int TAB_SUBKEYS = 2;
+ public static final int TAB_CERTS = 3;
// view
private ViewPager mViewPager;
private PagerSlidingTabStrip mSlidingTabLayout;
- private PagerTabStripAdapter mTabsAdapter;
private static final int LOADER_ID_UNIFIED = 0;
@@ -80,11 +81,8 @@ public class ViewKeyAdvActivity extends BaseActivity implements
mViewPager = (ViewPager) findViewById(R.id.pager);
mSlidingTabLayout = (PagerSlidingTabStrip) findViewById(R.id.sliding_tab_layout);
- int switchToTab = TAB_MAIN;
Intent intent = getIntent();
- if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) {
- switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
- }
+ int switchToTab = intent.getIntExtra(EXTRA_SELECTED_TAB, TAB_SHARE);
mDataUri = getIntent().getData();
if (mDataUri == null) {
@@ -102,8 +100,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements
}
}
- Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
-
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
@@ -120,34 +116,29 @@ public class ViewKeyAdvActivity extends BaseActivity implements
}
private void initTabs(Uri dataUri) {
- mTabsAdapter = new PagerTabStripAdapter(this);
- mViewPager.setAdapter(mTabsAdapter);
+ PagerTabStripAdapter adapter = new PagerTabStripAdapter(this);
+ mViewPager.setAdapter(adapter);
Bundle shareBundle = new Bundle();
shareBundle.putParcelable(ViewKeyAdvUserIdsFragment.ARG_DATA_URI, dataUri);
- mTabsAdapter.addTab(ViewKeyAdvShareFragment.class,
+ adapter.addTab(ViewKeyAdvShareFragment.class,
shareBundle, getString(R.string.key_view_tab_share));
Bundle userIdsBundle = new Bundle();
userIdsBundle.putParcelable(ViewKeyAdvUserIdsFragment.ARG_DATA_URI, dataUri);
- mTabsAdapter.addTab(ViewKeyAdvUserIdsFragment.class,
+ adapter.addTab(ViewKeyAdvUserIdsFragment.class,
userIdsBundle, getString(R.string.section_user_ids));
Bundle keysBundle = new Bundle();
keysBundle.putParcelable(ViewKeyAdvSubkeysFragment.ARG_DATA_URI, dataUri);
- mTabsAdapter.addTab(ViewKeyAdvSubkeysFragment.class,
+ adapter.addTab(ViewKeyAdvSubkeysFragment.class,
keysBundle, getString(R.string.key_view_tab_keys));
Bundle certsBundle = new Bundle();
certsBundle.putParcelable(ViewKeyAdvCertsFragment.ARG_DATA_URI, dataUri);
- mTabsAdapter.addTab(ViewKeyAdvCertsFragment.class,
+ adapter.addTab(ViewKeyAdvCertsFragment.class,
certsBundle, getString(R.string.key_view_tab_certs));
- Bundle trustBundle = new Bundle();
- trustBundle.putParcelable(ViewKeyTrustFragment.ARG_DATA_URI, dataUri);
- mTabsAdapter.addTab(ViewKeyTrustFragment.class,
- trustBundle, getString(R.string.key_view_tab_keybase));
-
// update layout after operations
mSlidingTabLayout.setViewPager(mViewPager);
}
@@ -185,11 +176,8 @@ public class ViewKeyAdvActivity extends BaseActivity implements
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- /* TODO better error handling? May cause problems when a key is deleted,
- * because the notification triggers faster than the activity closes.
- */
// Avoid NullPointerExceptions...
- if (data.getCount() == 0) {
+ if (data == null || data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
@@ -217,18 +205,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 fde0f62fd..4a46896bc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java
@@ -17,7 +17,17 @@
package org.sufficientlysecure.keychain.ui;
+
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+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;
@@ -42,72 +52,51 @@ 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.UncachedKeyRing;
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.KeychainContract.Keys;
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;
-import java.io.BufferedWriter;
-import java.io.OutputStreamWriter;
-import java.io.IOException;
-import java.io.FileNotFoundException;
-
public class ViewKeyAdvShareFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
- private TextView mFingerprint;
private ImageView mQrCode;
private CardView mQrCodeLayout;
- private View mFingerprintShareButton;
- private View mFingerprintClipboardButton;
- private View mKeyShareButton;
- private View mKeyClipboardButton;
- private View mKeyNfcButton;
- private ImageButton mKeySafeSlingerButton;
- private View mKeyUploadButton;
-
- ProviderHelper mProviderHelper;
+ private TextView mFingerprintView;
+
NfcHelper mNfcHelper;
private static final int LOADER_ID_UNIFIED = 0;
private Uri mDataUri;
+ private byte[] mFingerprint;
+ private long mMasterKeyId;
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_adv_share_fragment, getContainer());
- mProviderHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity());
- mNfcHelper = new NfcHelper(getActivity(), mProviderHelper);
+ ProviderHelper providerHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity());
+ mNfcHelper = new NfcHelper(getActivity(), providerHelper);
- mFingerprint = (TextView) view.findViewById(R.id.view_key_fingerprint);
+ mFingerprintView = (TextView) view.findViewById(R.id.view_key_fingerprint);
mQrCode = (ImageView) view.findViewById(R.id.view_key_qr_code);
mQrCodeLayout = (CardView) view.findViewById(R.id.view_key_qr_code_layout);
- mFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share);
- mFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard);
- mKeyShareButton = view.findViewById(R.id.view_key_action_key_share);
- mKeyNfcButton = view.findViewById(R.id.view_key_action_key_nfc);
- mKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
- mKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger);
- mKeyUploadButton = view.findViewById(R.id.view_key_action_upload);
-
- mKeySafeSlingerButton.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
- PorterDuff.Mode.SRC_IN);
-
mQrCodeLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -115,45 +104,67 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
});
- mFingerprintShareButton.setOnClickListener(new View.OnClickListener() {
+ 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(FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorTertiaryText),
+ PorterDuff.Mode.SRC_IN);
+
+ vFingerprintShareButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- share(mDataUri, mProviderHelper, true, false);
+ share(true, false);
}
});
- mFingerprintClipboardButton.setOnClickListener(new View.OnClickListener() {
+ vFingerprintClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- share(mDataUri, mProviderHelper, true, true);
+ share(true, true);
}
});
- mKeyShareButton.setOnClickListener(new View.OnClickListener() {
+ vKeyShareButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- share(mDataUri, mProviderHelper, false, false);
+ share(false, false);
}
});
- mKeyClipboardButton.setOnClickListener(new View.OnClickListener() {
+ vKeySafeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- share(mDataUri, mProviderHelper, false, true);
+ exportToFile();
}
});
-
- mKeyNfcButton.setOnClickListener(new View.OnClickListener() {
+ vKeyClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- mNfcHelper.invokeNfcBeam();
+ share(false, true);
}
});
- mKeySafeSlingerButton.setOnClickListener(new View.OnClickListener() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ vKeyNfcButton.setVisibility(View.VISIBLE);
+ vKeyNfcButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mNfcHelper.invokeNfcBeam();
+ }
+ });
+ } else {
+ vKeyNfcButton.setVisibility(View.GONE);
+ }
+
+ vKeySafeSlingerButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startSafeSlinger(mDataUri);
}
});
- mKeyUploadButton.setOnClickListener(new View.OnClickListener() {
+ vKeyUploadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
uploadToKeyserver();
@@ -163,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 {
@@ -177,97 +193,87 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
startActivityForResult(safeSlingerIntent, 0);
}
- private void share(Uri dataUri, ProviderHelper providerHelper, boolean fingerprintOnly,
- boolean toClipboard) {
+ private void share(boolean fingerprintOnly, boolean toClipboard) {
+ Activity activity = getActivity();
+ if (activity == null || mFingerprint == null) {
+ return;
+ }
+ ProviderHelper providerHelper = new ProviderHelper(activity);
+
try {
String content;
- byte[] fingerprintData = (byte[]) providerHelper.getGenericData(
- KeyRings.buildUnifiedKeyRingUri(dataUri),
- Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
if (fingerprintOnly) {
- String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintData);
+ String fingerprint = KeyFormattingUtils.convertFingerprintToHex(mFingerprint);
if (!toClipboard) {
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
} else {
content = fingerprint;
}
} else {
- Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri);
- // get public keyring as ascii armored string
- content = providerHelper.getKeyRingAsArmoredString(uri);
+ content = providerHelper.getKeyRingAsArmoredString(
+ KeychainContract.KeyRingData.buildPublicKeyRingUri(mDataUri));
}
if (toClipboard) {
- ClipboardReflection.copyToClipboard(getActivity(), content);
- String message;
- if (fingerprintOnly) {
- message = getResources().getString(R.string.fingerprint_copied_to_clipboard);
- } else {
- message = getResources().getString(R.string.key_copied_to_clipboard);
- }
- Notify.create(getActivity(), message, Notify.Style.OK).show();
- } else {
- // Android will fail with android.os.TransactionTooLargeException if key is too big
- // see http://www.lonestarprod.com/?p=34
- if (content.length() >= 86389) {
- Notify.create(getActivity(), R.string.key_too_big_for_sharing,
- Notify.Style.ERROR).show();
+ ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
+ if (clipMan == null) {
+ Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR);
return;
}
- // let user choose application
- Intent sendIntent = new Intent(Intent.ACTION_SEND);
- sendIntent.putExtra(Intent.EXTRA_TEXT, content);
- sendIntent.setType("text/plain");
+ ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, content);
+ clipMan.setPrimaryClip(clip);
- String title;
- if (fingerprintOnly) {
- title = getResources().getString(R.string.title_share_fingerprint_with);
- } else {
- title = getResources().getString(R.string.title_share_key);
- }
- Intent shareChooser = Intent.createChooser(sendIntent, title);
-
- // Bluetooth Share will convert text/plain sent via EXTRA_TEXT to HTML
- // Add replacement extra to send a text/plain file instead.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- try {
- String primaryUserId = UncachedKeyRing.decodeFromData(content.getBytes()).
- getPublicKey().getPrimaryUserIdWithFallback();
-
- TemporaryStorageProvider shareFileProv = new TemporaryStorageProvider();
- Uri contentUri = TemporaryStorageProvider.createFile(getActivity(),
- primaryUserId + Constants.FILE_EXTENSION_ASC);
-
- BufferedWriter contentWriter = new BufferedWriter(new OutputStreamWriter(
- new ParcelFileDescriptor.AutoCloseOutputStream(
- shareFileProv.openFile(contentUri, "w"))));
- contentWriter.write(content);
- contentWriter.close();
-
- // create replacement extras inside try{}:
- // if file creation fails, just don't add the replacements
- Bundle replacements = new Bundle();
- shareChooser.putExtra(Intent.EXTRA_REPLACEMENT_EXTRAS, replacements);
-
- Bundle bluetoothExtra = new Bundle(sendIntent.getExtras());
- replacements.putBundle("com.android.bluetooth", bluetoothExtra);
-
- bluetoothExtra.putParcelable(Intent.EXTRA_STREAM, contentUri);
- } catch (FileNotFoundException e) {
- Log.e(Constants.TAG, "error creating temporary Bluetooth key share file!", e);
- Notify.create(getActivity(), R.string.error_bluetooth_file, Notify.Style.ERROR).show();
- }
- }
+ Notify.create(activity, fingerprintOnly ? R.string.fingerprint_copied_to_clipboard
+ : R.string.key_copied_to_clipboard, Notify.Style.OK).show();
+ return;
+ }
+
+ // Android will fail with android.os.TransactionTooLargeException if key is too big
+ // see http://www.lonestarprod.com/?p=34
+ if (content.length() >= 86389) {
+ Notify.create(activity, R.string.key_too_big_for_sharing, Notify.Style.ERROR).show();
+ return;
+ }
- startActivity(shareChooser);
+ // let user choose application
+ Intent sendIntent = new Intent(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, content);
+ sendIntent.setType("text/plain");
+
+ // Bluetooth Share will convert text/plain sent via EXTRA_TEXT to HTML
+ // Add replacement extra to send a text/plain file instead.
+ try {
+ TemporaryStorageProvider shareFileProv = new TemporaryStorageProvider();
+ Uri contentUri = TemporaryStorageProvider.createFile(activity,
+ KeyFormattingUtils.convertFingerprintToHex(mFingerprint) + Constants.FILE_EXTENSION_ASC);
+
+ BufferedWriter contentWriter = new BufferedWriter(new OutputStreamWriter(
+ new ParcelFileDescriptor.AutoCloseOutputStream(
+ shareFileProv.openFile(contentUri, "w"))));
+ contentWriter.write(content);
+ contentWriter.close();
+
+ sendIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
+ } catch (FileNotFoundException e) {
+ Log.e(Constants.TAG, "error creating temporary Bluetooth key share file!", e);
+ // no need for a snackbar because one sharing option doesn't work
+ // Notify.create(getActivity(), R.string.error_temp_file, Notify.Style.ERROR).show();
}
+
+
+ String title = getString(fingerprintOnly
+ ? R.string.title_share_fingerprint_with : R.string.title_share_key);
+ Intent shareChooser = Intent.createChooser(sendIntent, title);
+
+ startActivity(shareChooser);
+
} catch (PgpGeneralException | IOException e) {
Log.e(Constants.TAG, "error processing key!", e);
- Notify.create(getActivity(), R.string.error_key_processing, Notify.Style.ERROR).show();
+ Notify.create(activity, R.string.error_key_processing, Notify.Style.ERROR).show();
} catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
- Notify.create(getActivity(), R.string.error_key_not_found, Notify.Style.ERROR).show();
+ Notify.create(activity, R.string.error_key_not_found, Notify.Style.ERROR).show();
}
}
@@ -288,8 +294,8 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
if (dataUri == null) {
@@ -304,8 +310,6 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
private void loadData(Uri dataUri) {
mDataUri = dataUri;
- Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
-
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
@@ -315,19 +319,10 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
static final String[] UNIFIED_PROJECTION = new String[] {
- KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET,
- KeyRings.USER_ID, KeyRings.FINGERPRINT,
- KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.IS_EXPIRED,
-
+ KeyRings._ID, KeyRings.FINGERPRINT
};
- static final int INDEX_UNIFIED_MASTER_KEY_ID = 1;
- static final int INDEX_UNIFIED_HAS_ANY_SECRET = 2;
- static final int INDEX_UNIFIED_USER_ID = 3;
- static final int INDEX_UNIFIED_FINGERPRINT = 4;
- static final int INDEX_UNIFIED_ALGORITHM = 5;
- static final int INDEX_UNIFIED_KEY_SIZE = 6;
- static final int INDEX_UNIFIED_CREATION = 7;
- static final int INDEX_UNIFIED_ID_EXPIRED = 8;
+
+ static final int INDEX_UNIFIED_FINGERPRINT = 1;
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
@@ -343,11 +338,8 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- /* TODO better error handling? May cause problems when a key is deleted,
- * because the notification triggers faster than the activity closes.
- */
// Avoid NullPointerExceptions...
- if (data.getCount() == 0) {
+ if (data == null || data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
@@ -357,10 +349,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
if (data.moveToFirst()) {
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
- String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
- mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
-
- loadQrCode(fingerprint);
+ setFingerprint(fingerprintBlob);
break;
}
@@ -375,20 +364,26 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
* We need to make sure we are no longer using it.
*/
public void onLoaderReset(Loader<Cursor> loader) {
+ mFingerprint = null;
}
- /**
- * Load QR Code asynchronously and with a fade in animation
- *
- * @param fingerprint
- */
- private void loadQrCode(final String fingerprint) {
+ /** 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));
+
AsyncTask<Void, Void, Bitmap> loadTask =
new AsyncTask<Void, Void, Bitmap>() {
protected Bitmap doInBackground(Void... unused) {
- String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
+ Uri uri = new Uri.Builder()
+ .scheme(Constants.FINGERPRINT_SCHEME)
+ .opaquePart(fingerprint)
+ .build();
// render with minimal size
- return QrCodeUtils.getQRCodeBitmap(qrCodeContent, 0);
+ return QrCodeUtils.getQRCodeBitmap(uri, 0);
}
protected void onPostExecute(Bitmap qrCode) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
index c01a94286..89e5d741f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
@@ -378,7 +378,7 @@ public class ViewKeyFragment extends LoaderFragment implements
* because the notification triggers faster than the activity closes.
*/
// Avoid NullPointerExceptions...
- if (data.getCount() == 0) {
+ if (data == null || data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java
index d5870d8c5..266633061 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java
@@ -17,15 +17,12 @@
package org.sufficientlysecure.keychain.ui;
-import android.app.ProgressDialog;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
-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;
@@ -48,24 +45,26 @@ import com.textuality.keybase.lib.User;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+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;
import java.util.List;
-public class ViewKeyTrustFragment extends LoaderFragment implements
- LoaderManager.LoaderCallbacks<Cursor> {
+public class ViewKeyKeybaseFragment extends LoaderFragment implements
+ LoaderManager.LoaderCallbacks<Cursor>,
+ CryptoOperationHelper.Callback<KeybaseVerificationParcel, KeybaseVerificationResult> {
public static final String ARG_DATA_URI = "uri";
- private View mStartSearch;
- private TextView mTrustReadout;
private TextView mReportHeader;
private TableLayout mProofListing;
private LayoutInflater mInflater;
@@ -77,15 +76,33 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
// for retrieving the key we’re working on
private Uri mDataUri;
+ private Proof mProof;
+
+ // for CryptoOperationHelper,Callback
+ private String mKeybaseProof;
+ private String mKeybaseFingerprint;
+ private CryptoOperationHelper<KeybaseVerificationParcel, KeybaseVerificationResult>
+ mKeybaseOpHelper;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ViewKeyKeybaseFragment newInstance(Uri dataUri) {
+ ViewKeyKeybaseFragment frag = new ViewKeyKeybaseFragment();
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_DATA_URI, dataUri);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_adv_keybase_fragment, getContainer());
mInflater = inflater;
- mTrustReadout = (TextView) view.findViewById(R.id.view_key_trust_readout);
- mStartSearch = view.findViewById(R.id.view_key_trust_search_cloud);
- mStartSearch.setEnabled(false);
mReportHeader = (TextView) view.findViewById(R.id.view_key_trust_cloud_narrative);
mProofListing = (TableLayout) view.findViewById(R.id.view_key_proof_list);
mProofVerifyHeader = view.findViewById(R.id.view_key_proof_verify_header);
@@ -148,58 +165,45 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
}
boolean nothingSpecial = true;
- StringBuilder message = new StringBuilder();
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
if (data.moveToFirst()) {
- if (data.getInt(INDEX_UNIFIED_HAS_ANY_SECRET) != 0) {
- message.append(getString(R.string.key_trust_it_is_yours)).append("\n");
- nothingSpecial = false;
- } else if (data.getInt(INDEX_VERIFIED) != 0) {
- message.append(getString(R.string.key_trust_already_verified)).append("\n");
- nothingSpecial = false;
- }
+ final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT);
+ final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp);
- // If this key is revoked, don’t trust it!
- if (data.getInt(INDEX_TRUST_IS_REVOKED) != 0) {
- message.append(getString(R.string.key_trust_revoked)).
- append(getString(R.string.key_trust_old_keys));
+ startSearch(fingerprint);
+ }
- nothingSpecial = false;
- } else {
- if (data.getInt(INDEX_TRUST_IS_EXPIRED) != 0) {
+ setContentShown(true);
+ }
- // if expired, don’t trust it!
- message.append(getString(R.string.key_trust_expired)).
- append(getString(R.string.key_trust_old_keys));
+ private void startSearch(final String fingerprint) {
+ final Preferences.ProxyPrefs proxyPrefs =
+ Preferences.getPreferences(getActivity()).getProxyPrefs();
- nothingSpecial = false;
- }
+ OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() {
+ @Override
+ public void onOrbotStarted() {
+ new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
}
- if (nothingSpecial) {
- message.append(getString(R.string.key_trust_maybe));
+ @Override
+ public void onNeutralButton() {
+ new DescribeKey(ParcelableProxy.getForNoProxy())
+ .execute(fingerprint);
}
- final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT);
- final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp);
- if (fingerprint != null) {
+ @Override
+ public void onCancel() {
- mStartSearch.setEnabled(true);
- mStartSearch.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- mStartSearch.setEnabled(false);
- new DescribeKey().execute(fingerprint);
- }
- });
}
- }
+ };
- mTrustReadout.setText(message);
- setContentShown(true);
+ if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) {
+ new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
+ }
}
/**
@@ -223,6 +227,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) {
@@ -231,7 +240,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);
@@ -243,8 +252,6 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
Proof[] proofsFor = proofs.get(proofType).toArray(x);
if (proofsFor.length > 0) {
SpannableStringBuilder ssb = new SpannableStringBuilder();
- ssb.append(getProofNarrative(proofType)).append(" ");
-
int i = 0;
while (i < proofsFor.length - 1) {
appendProofLinks(ssb, fingerprint, proofsFor[i]);
@@ -252,7 +259,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
i++;
}
appendProofLinks(ssb, fingerprint, proofsFor[i]);
- proofList.add(ssb);
+ proofList.add(formatSpannableString(ssb, getProofNarrative(proofType)));
}
}
@@ -262,6 +269,20 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
return new ResultPage(getString(R.string.key_trust_results_prefix), proofList);
}
+ private SpannableStringBuilder formatSpannableString(SpannableStringBuilder proofLinks, String proofType) {
+ //Formatting SpannableStringBuilder with String.format() causes the links to stop working.
+ //This method is to insert the links while reserving the links
+
+ SpannableStringBuilder ssb = new SpannableStringBuilder();
+ ssb.append(proofType);
+ if (proofType.contains("%s")) {
+ int i = proofType.indexOf("%s");
+ ssb.replace(i, i + 2, proofLinks);
+ } else ssb.append(proofLinks);
+
+ return ssb;
+ }
+
private SpannableStringBuilder appendProofLinks(SpannableStringBuilder ssb, final String fingerprint, final Proof proof) throws KeybaseException {
int startAt = ssb.length();
String handle = proof.getHandle();
@@ -291,7 +312,6 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
result.mHeader = getActivity().getString(R.string.key_trust_no_cloud_evidence);
}
- mStartSearch.setVisibility(View.GONE);
mReportHeader.setVisibility(View.VISIBLE);
mProofListing.setVisibility(View.VISIBLE);
mReportHeader.setText(result.mHeader);
@@ -306,22 +326,35 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
text.setMovementMethod(LinkMovementMethod.getInstance());
mProofListing.addView(row);
}
-
- // mSearchReport.loadDataWithBaseURL("file:///android_res/drawable/", s, "text/html", "UTF-8", null);
}
}
private String getProofNarrative(int proofType) {
int stringIndex;
switch (proofType) {
- case Proof.PROOF_TYPE_TWITTER: stringIndex = R.string.keybase_narrative_twitter; break;
- case Proof.PROOF_TYPE_GITHUB: stringIndex = R.string.keybase_narrative_github; break;
- case Proof.PROOF_TYPE_DNS: stringIndex = R.string.keybase_narrative_dns; break;
- case Proof.PROOF_TYPE_WEB_SITE: stringIndex = R.string.keybase_narrative_web_site; break;
- case Proof.PROOF_TYPE_HACKERNEWS: stringIndex = R.string.keybase_narrative_hackernews; break;
- case Proof.PROOF_TYPE_COINBASE: stringIndex = R.string.keybase_narrative_coinbase; break;
- case Proof.PROOF_TYPE_REDDIT: stringIndex = R.string.keybase_narrative_reddit; break;
- default: stringIndex = R.string.keybase_narrative_unknown;
+ case Proof.PROOF_TYPE_TWITTER:
+ stringIndex = R.string.keybase_narrative_twitter;
+ break;
+ case Proof.PROOF_TYPE_GITHUB:
+ stringIndex = R.string.keybase_narrative_github;
+ break;
+ case Proof.PROOF_TYPE_DNS:
+ stringIndex = R.string.keybase_narrative_dns;
+ break;
+ case Proof.PROOF_TYPE_WEB_SITE:
+ stringIndex = R.string.keybase_narrative_web_site;
+ break;
+ case Proof.PROOF_TYPE_HACKERNEWS:
+ stringIndex = R.string.keybase_narrative_hackernews;
+ break;
+ case Proof.PROOF_TYPE_COINBASE:
+ stringIndex = R.string.keybase_narrative_coinbase;
+ break;
+ case Proof.PROOF_TYPE_REDDIT:
+ stringIndex = R.string.keybase_narrative_reddit;
+ break;
+ default:
+ stringIndex = R.string.keybase_narrative_unknown;
}
return getActivity().getString(stringIndex);
}
@@ -338,125 +371,150 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
// which proofs do we have working verifiers for?
private boolean haveProofFor(int proofType) {
switch (proofType) {
- case Proof.PROOF_TYPE_TWITTER: return true;
- case Proof.PROOF_TYPE_GITHUB: return true;
- case Proof.PROOF_TYPE_DNS: return true;
- case Proof.PROOF_TYPE_WEB_SITE: return true;
- case Proof.PROOF_TYPE_HACKERNEWS: return true;
- case Proof.PROOF_TYPE_COINBASE: return true;
- case Proof.PROOF_TYPE_REDDIT: return true;
- default: return false;
+ case Proof.PROOF_TYPE_TWITTER:
+ return true;
+ case Proof.PROOF_TYPE_GITHUB:
+ return true;
+ case Proof.PROOF_TYPE_DNS:
+ return true;
+ case Proof.PROOF_TYPE_WEB_SITE:
+ return true;
+ case Proof.PROOF_TYPE_HACKERNEWS:
+ return true;
+ case Proof.PROOF_TYPE_COINBASE:
+ return true;
+ case Proof.PROOF_TYPE_REDDIT:
+ return true;
+ default:
+ return false;
}
}
private void verify(final Proof proof, final String fingerprint) {
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
- Bundle data = new Bundle();
- intent.setAction(KeychainIntentService.ACTION_VERIFY_KEYBASE_PROOF);
- data.putString(KeychainIntentService.KEYBASE_PROOF, proof.toString());
- data.putString(KeychainIntentService.KEYBASE_REQUIRED_FINGERPRINT, fingerprint);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+ mProof = proof;
+ mKeybaseProof = proof.toString();
+ mKeybaseFingerprint = fingerprint;
mProofVerifyDetail.setVisibility(View.GONE);
- // Create a new Messenger for the communication back after proof work is done
- //
- ServiceProgressHandler handler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_verifying_signature),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- Bundle returnData = message.getData();
- String msg = returnData.getString(ServiceProgressHandler.DATA_MESSAGE);
- SpannableStringBuilder ssb = new SpannableStringBuilder();
-
- if ((msg != null) && msg.equals("OK")) {
-
- //yay
- String proofUrl = returnData.getString(ServiceProgressHandler.KEYBASE_PROOF_URL);
- String presenceUrl = returnData.getString(ServiceProgressHandler.KEYBASE_PRESENCE_URL);
- String presenceLabel = returnData.getString(ServiceProgressHandler.KEYBASE_PRESENCE_LABEL);
-
- String proofLabel;
- switch (proof.getType()) {
- case Proof.PROOF_TYPE_TWITTER:
- proofLabel = getString(R.string.keybase_twitter_proof);
- break;
- case Proof.PROOF_TYPE_DNS:
- proofLabel = getString(R.string.keybase_dns_proof);
- break;
- case Proof.PROOF_TYPE_WEB_SITE:
- proofLabel = getString(R.string.keybase_web_site_proof);
- break;
- case Proof.PROOF_TYPE_GITHUB:
- proofLabel = getString(R.string.keybase_github_proof);
- break;
- case Proof.PROOF_TYPE_REDDIT:
- proofLabel = getString(R.string.keybase_reddit_proof);
- break;
- default:
- proofLabel = getString(R.string.keybase_a_post);
- break;
- }
+ mKeybaseOpHelper = new CryptoOperationHelper<>(1, this, this,
+ R.string.progress_verifying_signature);
+ mKeybaseOpHelper.cryptoOperation();
+ }
- ssb.append(getString(R.string.keybase_proof_succeeded));
- StyleSpan bold = new StyleSpan(Typeface.BOLD);
- ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- ssb.append("\n\n");
- int length = ssb.length();
- ssb.append(proofLabel);
- if (proofUrl != null) {
- URLSpan postLink = new URLSpan(proofUrl);
- ssb.setSpan(postLink, length, length + proofLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- if (Proof.PROOF_TYPE_DNS == proof.getType()) {
- ssb.append(" ").append(getString(R.string.keybase_for_the_domain)).append(" ");
- } else {
- ssb.append(" ").append(getString(R.string.keybase_fetched_from)).append(" ");
- }
- length = ssb.length();
- URLSpan presenceLink = new URLSpan(presenceUrl);
- ssb.append(presenceLabel);
- ssb.setSpan(presenceLink, length, length + presenceLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- if (Proof.PROOF_TYPE_REDDIT == proof.getType()) {
- ssb.append(", ").
- append(getString(R.string.keybase_reddit_attribution)).
- append(" “").append(proof.getHandle()).append("”, ");
- }
- ssb.append(" ").append(getString(R.string.keybase_contained_signature));
- } else {
- // verification failed!
- msg = returnData.getString(ServiceProgressHandler.DATA_ERROR);
- ssb.append(getString(R.string.keybase_proof_failure));
- if (msg == null) {
- msg = getString(R.string.keybase_unknown_proof_failure);
- }
- StyleSpan bold = new StyleSpan(Typeface.BOLD);
- ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- ssb.append("\n\n").append(msg);
- }
- mProofVerifyHeader.setVisibility(View.VISIBLE);
- mProofVerifyDetail.setVisibility(View.VISIBLE);
- mProofVerifyDetail.setMovementMethod(LinkMovementMethod.getInstance());
- mProofVerifyDetail.setText(ssb);
- }
- }
- };
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (mKeybaseOpHelper != null) {
+ mKeybaseOpHelper.handleActivityResult(requestCode, resultCode, data);
+ }
+ }
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(handler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+ // CryptoOperationHelper.Callback methods
+ @Override
+ public KeybaseVerificationParcel createOperationInput() {
+ return new KeybaseVerificationParcel(mKeybaseProof, mKeybaseFingerprint);
+ }
- // show progress dialog
- handler.showProgressDialog(getActivity());
+ @Override
+ public void onCryptoOperationSuccess(KeybaseVerificationResult result) {
+
+ result.createNotify(getActivity()).show();
+
+ String proofUrl = result.mProofUrl;
+ String presenceUrl = result.mPresenceUrl;
+ String presenceLabel = result.mPresenceLabel;
+
+ Proof proof = mProof; // TODO: should ideally be contained in result
+
+ String proofLabel;
+ switch (proof.getType()) {
+ case Proof.PROOF_TYPE_TWITTER:
+ proofLabel = getString(R.string.keybase_twitter_proof);
+ break;
+ case Proof.PROOF_TYPE_DNS:
+ proofLabel = getString(R.string.keybase_dns_proof);
+ break;
+ case Proof.PROOF_TYPE_WEB_SITE:
+ proofLabel = getString(R.string.keybase_web_site_proof);
+ break;
+ case Proof.PROOF_TYPE_GITHUB:
+ proofLabel = getString(R.string.keybase_github_proof);
+ break;
+ case Proof.PROOF_TYPE_REDDIT:
+ proofLabel = getString(R.string.keybase_reddit_proof);
+ break;
+ default:
+ proofLabel = getString(R.string.keybase_a_post);
+ break;
+ }
+
+ SpannableStringBuilder ssb = new SpannableStringBuilder();
+
+ ssb.append(getString(R.string.keybase_proof_succeeded));
+ StyleSpan bold = new StyleSpan(Typeface.BOLD);
+ ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ ssb.append("\n\n");
+ int length = ssb.length();
+ ssb.append(proofLabel);
+ if (proofUrl != null) {
+ URLSpan postLink = new URLSpan(proofUrl);
+ ssb.setSpan(postLink, length, length + proofLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ if (Proof.PROOF_TYPE_DNS == proof.getType()) {
+ ssb.append(" ").append(getString(R.string.keybase_for_the_domain)).append(" ");
+ } else {
+ ssb.append(" ").append(getString(R.string.keybase_fetched_from)).append(" ");
+ }
+ length = ssb.length();
+ URLSpan presenceLink = new URLSpan(presenceUrl);
+ ssb.append(presenceLabel);
+ ssb.setSpan(presenceLink, length, length + presenceLabel.length(), Spanned
+ .SPAN_EXCLUSIVE_EXCLUSIVE);
+ if (Proof.PROOF_TYPE_REDDIT == proof.getType()) {
+ ssb.append(", ").
+ append(getString(R.string.keybase_reddit_attribution)).
+ append(" “").append(proof.getHandle()).append("”, ");
+ }
+ ssb.append(" ").append(getString(R.string.keybase_contained_signature));
+
+ displaySpannableResult(ssb);
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+
+ }
+
+ @Override
+ public void onCryptoOperationError(KeybaseVerificationResult result) {
+
+ result.createNotify(getActivity()).show();
+
+ SpannableStringBuilder ssb = new SpannableStringBuilder();
+
+ ssb.append(getString(R.string.keybase_proof_failure));
+ String msg = getString(result.getLog().getLast().mType.mMsgId);
+ if (msg == null) {
+ msg = getString(R.string.keybase_unknown_proof_failure);
+ }
+ StyleSpan bold = new StyleSpan(Typeface.BOLD);
+ ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ ssb.append("\n\n").append(msg);
+
+ displaySpannableResult(ssb);
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
- // start service with intent
- getActivity().startService(intent);
+ private void displaySpannableResult(SpannableStringBuilder ssb) {
+ mProofVerifyHeader.setVisibility(View.VISIBLE);
+ mProofVerifyDetail.setVisibility(View.VISIBLE);
+ mProofVerifyDetail.setMovementMethod(LinkMovementMethod.getInstance());
+ mProofVerifyDetail.setText(ssb);
}
}
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 812874456..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,15 +18,12 @@
package org.sufficientlysecure.keychain.ui;
+
import java.nio.ByteBuffer;
import java.util.Arrays;
-import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
-import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
@@ -39,31 +36,38 @@ import android.widget.TextView;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
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.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.service.PromoteKeyringParcel;
+import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
-public class ViewKeyYubiKeyFragment extends Fragment
+
+public class ViewKeyYubiKeyFragment
+ extends QueueingCryptoOperationFragment<PromoteKeyringParcel, PromoteKeyResult>
implements LoaderCallbacks<Cursor> {
+ public static final String ARG_MASTER_KEY_ID = "master_key_id";
public static final String ARG_FINGERPRINT = "fingerprint";
public static final String ARG_USER_ID = "user_id";
public static final String ARG_CARD_AID = "aid";
+
private byte[][] mFingerprints;
private String mUserId;
private byte[] mCardAid;
private long mMasterKeyId;
+ private long[] mSubKeyIds;
+
private Button vButton;
private TextView vStatus;
- public static ViewKeyYubiKeyFragment newInstance(byte[] fingerprints, String userId, byte[] aid) {
+ public static ViewKeyYubiKeyFragment newInstance(long masterKeyId,
+ byte[] fingerprints, String userId, byte[] aid) {
ViewKeyYubiKeyFragment frag = new ViewKeyYubiKeyFragment();
Bundle args = new Bundle();
+ args.putLong(ARG_MASTER_KEY_ID, masterKeyId);
args.putByteArray(ARG_FINGERPRINT, fingerprints);
args.putString(ARG_USER_ID, userId);
args.putByteArray(ARG_CARD_AID, aid);
@@ -72,13 +76,17 @@ public class ViewKeyYubiKeyFragment extends Fragment
return frag;
}
+ public ViewKeyYubiKeyFragment() {
+ super(null);
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
ByteBuffer buf = ByteBuffer.wrap(args.getByteArray(ARG_FINGERPRINT));
- mFingerprints = new byte[buf.remaining()/40][];
+ mFingerprints = new byte[buf.remaining()/20][];
for (int i = 0; i < mFingerprints.length; i++) {
mFingerprints[i] = new byte[20];
buf.get(mFingerprints[i]);
@@ -86,7 +94,7 @@ public class ViewKeyYubiKeyFragment extends Fragment
mUserId = args.getString(ARG_USER_ID);
mCardAid = args.getByteArray(ARG_CARD_AID);
- mMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[0]);
+ mMasterKeyId = args.getLong(ARG_MASTER_KEY_ID);
getLoaderManager().initLoader(0, null, this);
@@ -105,7 +113,7 @@ public class ViewKeyYubiKeyFragment extends Fragment
if (!mUserId.isEmpty()) {
vUserId.setText(getString(R.string.yubikey_key_holder, mUserId));
} else {
- vUserId.setText(getString(R.string.yubikey_key_holder_unset));
+ vUserId.setText(getString(R.string.yubikey_key_holder_not_set));
}
vButton = (Button) view.findViewById(R.id.button_bind);
@@ -122,44 +130,15 @@ public class ViewKeyYubiKeyFragment extends Fragment
}
public void promoteToSecretKey() {
+ long[] subKeyIds = new long[mFingerprints.length];
+ for (int i = 0; i < subKeyIds.length; i++) {
+ subKeyIds[i] = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[i]);
+ }
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
-
- PromoteKeyResult result =
- returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
-
- result.createNotify(getActivity()).show();
- }
-
- }
- };
-
- // Send all information needed to service to decrypt in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
-
- // fill values for this action
-
- intent.setAction(KeychainIntentService.ACTION_PROMOTE_KEYRING);
-
- Bundle data = new Bundle();
- data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mMasterKeyId);
- data.putByteArray(KeychainIntentService.PROMOTE_CARD_AID, mCardAid);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // start service with intent
- getActivity().startService(intent);
+ // mMasterKeyId and mCardAid are already set
+ mSubKeyIds = subKeyIds;
+ cryptoOperation();
}
public static final String[] PROJECTION = new String[]{
@@ -169,8 +148,8 @@ public class ViewKeyYubiKeyFragment extends Fragment
Keys.HAS_SECRET,
Keys.FINGERPRINT
};
- private static final int INDEX_KEY_ID = 1;
- private static final int INDEX_RANK = 2;
+ // private static final int INDEX_KEY_ID = 1;
+ // private static final int INDEX_RANK = 2;
private static final int INDEX_HAS_SECRET = 3;
private static final int INDEX_FINGERPRINT = 4;
@@ -216,7 +195,7 @@ public class ViewKeyYubiKeyFragment extends Fragment
}
- public Integer naiveIndexOf(byte[][] haystack, byte[] needle) {
+ static private Integer naiveIndexOf(byte[][] haystack, byte[] needle) {
for (int i = 0; i < haystack.length; i++) {
if (Arrays.equals(needle, haystack[i])) {
return i;
@@ -229,4 +208,15 @@ public class ViewKeyYubiKeyFragment extends Fragment
public void onLoaderReset(Loader<Cursor> loader) {
}
+
+ @Override
+ public PromoteKeyringParcel createOperationInput() {
+ return new PromoteKeyringParcel(mMasterKeyId, mCardAid, mSubKeyIds);
+ }
+
+ @Override
+ 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 db88de676..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
@@ -33,6 +33,7 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
+import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Highlighter;
@@ -92,8 +93,8 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
}
/** This method returns a list of all selected entries, with public keys sorted
- * before secret keys, see ImportExportOperation for specifics.
- * @see org.sufficientlysecure.keychain.operations.ImportExportOperation
+ * before secret keys, see ImportOperation for specifics.
+ * @see ImportOperation
*/
public ArrayList<ImportKeysListEntry> getSelectedEntries() {
ArrayList<ImportKeysListEntry> result = new ArrayList<>();
@@ -176,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()) {
@@ -187,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);
@@ -197,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) {
@@ -241,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);
@@ -257,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 4781864dd..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
@@ -109,7 +149,7 @@ public class ImportKeysListCloudLoader
ImportKeysListEntry uniqueEntry = searchResult.get(0);
/*
* set fingerprint explicitly after query
- * to enforce a check when the key is imported by KeychainIntentService
+ * to enforce a check when the key is imported by KeychainService
*/
uniqueEntry.setFingerprintHex(fingerprint);
uniqueEntry.setSelected(true);
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 3dbae09b6..56d273c7c 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
@@ -18,6 +18,11 @@
package org.sufficientlysecure.keychain.ui.adapter;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
import java.util.Date;
import android.content.Context;
@@ -27,6 +32,7 @@ import android.support.v4.widget.CursorAdapter;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
@@ -38,6 +44,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;
@@ -45,6 +52,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[]{
@@ -56,7 +64,6 @@ public class KeyAdapter extends CursorAdapter {
KeyRings.VERIFIED,
KeyRings.HAS_ANY_SECRET,
KeyRings.HAS_DUPLICATE_USER_ID,
- KeyRings.HAS_ENCRYPT,
KeyRings.FINGERPRINT,
KeyRings.CREATION,
};
@@ -68,13 +75,13 @@ public class KeyAdapter extends CursorAdapter {
public static final int INDEX_VERIFIED = 5;
public static final int INDEX_HAS_ANY_SECRET = 6;
public static final int INDEX_HAS_DUPLICATE_USER_ID = 7;
- public static final int INDEX_HAS_ENCRYPT = 8;
- public static final int INDEX_FINGERPRINT = 9;
- public static final int INDEX_CREATION = 10;
+ public static final int INDEX_FINGERPRINT = 8;
+ public static final int INDEX_CREATION = 9;
public KeyAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
+ mContext = context;
mInflater = LayoutInflater.from(context);
}
@@ -83,6 +90,9 @@ public class KeyAdapter extends CursorAdapter {
}
public static class KeyItemViewHolder {
+ public View mView;
+ public View mLayoutDummy;
+ public View mLayoutData;
public Long mMasterKeyId;
public TextView mMainUserId;
public TextView mMainUserIdRest;
@@ -91,7 +101,12 @@ public class KeyAdapter extends CursorAdapter {
public View mSlinger;
public ImageButton mSlingerButton;
+ public KeyItem mDisplayedItem;
+
public KeyItemViewHolder(View view) {
+ mView = view;
+ mLayoutData = view.findViewById(R.id.key_list_item_data);
+ mLayoutDummy = view.findViewById(R.id.key_list_item_dummy);
mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon);
@@ -100,11 +115,15 @@ public class KeyAdapter extends CursorAdapter {
mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation);
}
- public void setData(Context context, Cursor cursor, Highlighter highlighter) {
+ public void setData(Context context, KeyItem item, Highlighter highlighter, boolean enabled) {
+
+ mLayoutData.setVisibility(View.VISIBLE);
+ mLayoutDummy.setVisibility(View.GONE);
+
+ mDisplayedItem = item;
{ // set name and stuff, common to both key types
- String userId = cursor.getString(INDEX_USER_ID);
- KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
+ KeyRing.UserId userIdSplit = item.mUserId;
if (userIdSplit.name != null) {
mMainUserId.setText(highlighter.highlight(userIdSplit.name));
} else {
@@ -118,43 +137,42 @@ public class KeyAdapter extends CursorAdapter {
}
}
+ // sort of a hack: if this item isn't enabled, we make it clickable
+ // to intercept its click events. either way, no listener!
+ mView.setClickable(!enabled);
+
{ // set edit button and status, specific by key type
- long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
- boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
- boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
- boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0;
- boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;
- boolean hasDuplicate = cursor.getInt(INDEX_HAS_DUPLICATE_USER_ID) != 0;
+ mMasterKeyId = item.mKeyId;
- mMasterKeyId = masterKeyId;
+ int textColor;
// Note: order is important!
- if (isRevoked) {
+ 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));
- } else if (isExpired) {
- KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, 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.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));
- } else if (isSecret) {
+ textColor = context.getResources().getColor(R.color.key_flag_gray);
+ } else if (item.mIsSecret) {
mStatus.setVisibility(View.GONE);
if (mSlingerButton.hasOnClickListeners()) {
+ mSlingerButton.setColorFilter(
+ 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 (isVerified) {
+ if (item.mIsVerified) {
KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED);
mStatus.setVisibility(View.VISIBLE);
} else {
@@ -162,19 +180,26 @@ 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 (hasDuplicate) {
+ 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,
- cursor.getLong(INDEX_CREATION) * 1000,
+ item.mCreation.getTime(),
DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR
| DateUtils.FORMAT_ABBREV_MONTH);
mCreationDate.setText(context.getString(R.string.label_key_created,
- dateTime));
+ dateTime));
+ mCreationDate.setTextColor(textColor);
mCreationDate.setVisibility(View.VISIBLE);
} else {
mCreationDate.setVisibility(View.GONE);
@@ -184,6 +209,24 @@ public class KeyAdapter extends CursorAdapter {
}
+ /** Shows the "you have no keys yet" dummy view, and sets an OnClickListener. */
+ public void setDummy(OnClickListener listener) {
+
+ // just reset everything to display the dummy layout
+ mLayoutDummy.setVisibility(View.VISIBLE);
+ mLayoutData.setVisibility(View.GONE);
+ mSlinger.setVisibility(View.GONE);
+ mStatus.setVisibility(View.GONE);
+ mView.setClickable(false);
+
+ mLayoutDummy.setOnClickListener(listener);
+
+ }
+
+ }
+
+ public boolean isEnabled(Cursor cursor) {
+ return true;
}
@Override
@@ -191,16 +234,17 @@ public class KeyAdapter extends CursorAdapter {
View view = mInflater.inflate(R.layout.key_list_item, parent, false);
KeyItemViewHolder holder = new KeyItemViewHolder(view);
view.setTag(holder);
- holder.mSlingerButton.setColorFilter(context.getResources().getColor(R.color.tertiary_text_light),
- PorterDuff.Mode.SRC_IN);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
Highlighter highlighter = new Highlighter(context, mQuery);
+ KeyItem item = new KeyItem(cursor);
+ boolean isEnabled = isEnabled(cursor);
+
KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
- h.setData(context, cursor, highlighter);
+ h.setData(context, item, highlighter, isEnabled);
}
public boolean isSecretAvailable(int id) {
@@ -230,14 +274,16 @@ public class KeyAdapter extends CursorAdapter {
@Override
public long getItemId(int position) {
+ Cursor cursor = getCursor();
// prevent a crash on rapid cursor changes
- if (getCursor().isClosed()) {
+ if (cursor != null && getCursor().isClosed()) {
return 0L;
}
return super.getItemId(position);
}
- public static class KeyItem {
+ // must be serializable for TokenCompleTextView state
+ public static class KeyItem implements Serializable {
public final String mUserIdFull;
public final KeyRing.UserId mUserId;
@@ -245,6 +291,7 @@ public class KeyAdapter extends CursorAdapter {
public final boolean mHasDuplicate;
public final Date mCreation;
public final String mFingerprint;
+ public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified;
private KeyItem(Cursor cursor) {
String userId = cursor.getString(INDEX_USER_ID);
@@ -255,6 +302,10 @@ public class KeyAdapter extends CursorAdapter {
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
cursor.getBlob(INDEX_FINGERPRINT));
+ mIsSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
+ mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
+ mIsExpired = cursor.getInt(INDEX_IS_EXPIRED) > 0;
+ mIsVerified = cursor.getInt(INDEX_VERIFIED) > 0;
}
public KeyItem(CanonicalizedPublicKeyRing ring) {
@@ -267,6 +318,12 @@ public class KeyAdapter extends CursorAdapter {
mCreation = key.getCreationTime();
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
ring.getFingerprint());
+ mIsRevoked = key.isRevoked();
+ mIsExpired = key.isExpired();
+
+ // these two are actually "don't know"s
+ mIsSecret = false;
+ mIsVerified = false;
}
public String getReadableName() {
@@ -279,4 +336,11 @@ public class KeyAdapter extends CursorAdapter {
}
+ public static String[] getProjectionWith(String[] projection) {
+ List<String> list = new ArrayList<>();
+ list.addAll(Arrays.asList(PROJECTION));
+ list.addAll(Arrays.asList(projection));
+ return list.toArray(new String[list.size()]);
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java
new file mode 100644
index 000000000..471a20411
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java
@@ -0,0 +1,87 @@
+package org.sufficientlysecure.keychain.ui.adapter;
+
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v7.internal.widget.AdapterViewCompat;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.CheckBox;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+public class KeySelectableAdapter extends KeyAdapter implements OnItemClickListener {
+
+ HashSet<Long> mSelectedItems = new HashSet<>();
+
+ public KeySelectableAdapter(Context context, Cursor c, int flags, Set<Long> initialChecked) {
+ super(context, c, flags);
+ if (initialChecked != null) {
+ mSelectedItems.addAll(initialChecked);
+ }
+ }
+
+ public static class KeySelectableItemViewHolder extends KeyItemViewHolder {
+
+ public CheckBox mCheckbox;
+
+ public KeySelectableItemViewHolder(View view) {
+ super(view);
+ mCheckbox = (CheckBox) view.findViewById(R.id.selected);
+ }
+
+ public void setCheckedState(boolean checked) {
+ mCheckbox.setChecked(checked);
+ }
+
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View view = mInflater.inflate(R.layout.key_list_selectable_item, parent, false);
+ KeySelectableItemViewHolder holder = new KeySelectableItemViewHolder(view);
+ view.setTag(holder);
+ return view;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ super.bindView(view, context, cursor);
+
+ KeySelectableItemViewHolder h = (KeySelectableItemViewHolder) view.getTag();
+ h.setCheckedState(mSelectedItems.contains(h.mDisplayedItem.mKeyId));
+
+ }
+
+ public void setCheckedStates(Set<Long> checked) {
+ mSelectedItems.clear();
+ mSelectedItems.addAll(checked);
+ notifyDataSetChanged();
+ }
+
+ public Set<Long> getSelectedMasterKeyIds() {
+ return Collections.unmodifiableSet(mSelectedItems);
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ Log.d(Constants.TAG, "clicked id: " + id);
+ long masterKeyId = getMasterKeyId(position);
+ if (mSelectedItems.contains(masterKeyId)) {
+ mSelectedItems.remove(masterKeyId);
+ } else {
+ mSelectedItems.add(masterKeyId);
+ }
+ notifyDataSetChanged();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java
index 5218273a0..b91abf076 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java
@@ -40,20 +40,19 @@ public class MultiUserIdsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private final ArrayList<Boolean> mCheckStates;
- public MultiUserIdsAdapter(Context context, Cursor c, int flags) {
+ public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
- mCheckStates = new ArrayList<>();
+ mCheckStates = preselectStates == null ? new ArrayList<Boolean>() : preselectStates;
}
@Override
public Cursor swapCursor(Cursor newCursor) {
- mCheckStates.clear();
if (newCursor != null) {
int count = newCursor.getCount();
mCheckStates.ensureCapacity(count);
- // initialize to true (use case knowledge: we usually want to sign all uids)
- for (int i = 0; i < count; i++) {
+ // initialize new fields to true (use case knowledge: we usually want to sign all uids)
+ for (int i = mCheckStates.size(); i < count; i++) {
mCheckStates.add(true);
}
}
@@ -151,6 +150,10 @@ public class MultiUserIdsAdapter extends CursorAdapter {
}
+ public ArrayList<Boolean> getCheckStates() {
+ return mCheckStates;
+ }
+
public ArrayList<CertifyAction> getSelectedCertifyActions() {
LongSparseArray<CertifyAction> actions = new LongSparseArray<>();
for (int i = 0; i < mCheckStates.size(); i++) {
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 6bbf41a88..f01f25200 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 096dea51f..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
@@ -116,6 +116,21 @@ public class SubkeysAdapter extends CursorAdapter {
}
}
+ public int getAlgorithm(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getInt(INDEX_ALGORITHM);
+ }
+
+ public int getKeySize(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getInt(INDEX_KEY_SIZE);
+ }
+
+ public SecretKeyType getSecretKeyType(int position) {
+ mCursor.moveToPosition(position);
+ return SecretKeyType.fromNum(mCursor.getInt(INDEX_HAS_SECRET));
+ }
+
@Override
public Cursor swapCursor(Cursor newCursor) {
hasAnySecret = false;
@@ -164,13 +179,23 @@ public class SubkeysAdapter extends CursorAdapter {
? mSaveKeyringParcel.getSubkeyChange(keyId)
: null;
- if (change != null && change.mDummyStrip) {
- algorithmStr.append(", ");
- final SpannableString boldStripped = new SpannableString(
- context.getString(R.string.key_stripped)
- );
- boldStripped.setSpan(new StyleSpan(Typeface.BOLD), 0, boldStripped.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- algorithmStr.append(boldStripped);
+ if (change != null && (change.mDummyStrip || change.mMoveKeyToCard)) {
+ if (change.mDummyStrip) {
+ algorithmStr.append(", ");
+ final SpannableString boldStripped = new SpannableString(
+ context.getString(R.string.key_stripped)
+ );
+ boldStripped.setSpan(new StyleSpan(Typeface.BOLD), 0, boldStripped.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ algorithmStr.append(boldStripped);
+ }
+ if (change.mMoveKeyToCard) {
+ algorithmStr.append(", ");
+ final SpannableString boldDivert = new SpannableString(
+ context.getString(R.string.key_divert)
+ );
+ boldDivert.setSpan(new StyleSpan(Typeface.BOLD), 0, boldDivert.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ algorithmStr.append(boldDivert);
+ }
} else {
switch (SecretKeyType.fromNum(cursor.getInt(INDEX_HAS_SECRET))) {
case GNU_DUMMY:
@@ -259,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 c68c078ad..0f4312dad 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 0e752881f..aa4e7d840 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,8 @@ import android.view.ViewGroup;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
/**
* Setups Toolbar
@@ -36,15 +39,33 @@ 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) {
+ initTheme();
super.onCreate(savedInstanceState);
initLayout();
initToolbar();
}
- protected abstract void initLayout();
+ @Override
+ protected void onResume() {
+ super.onResume();
+ KeyserverSyncAdapterService.cancelUpdates(this);
+
+ if (mThemeChanger.changeTheme()) {
+ Intent intent = getIntent();
+ finish();
+ overridePendingTransition(0, 0);
+ startActivity(intent);
+ overridePendingTransition(0, 0);
+ }
+ }
+
+ protected void initLayout() {
+
+ }
protected void initToolbar() {
mToolbar = (Toolbar) findViewById(R.id.toolbar);
@@ -55,6 +76,15 @@ public abstract class BaseActivity extends AppCompatActivity {
mStatusBar = findViewById(R.id.status_bar);
}
+ /**
+ * Override if you want a different theme!
+ */
+ protected void initTheme() {
+ mThemeChanger = new ThemeChanger(this);
+ mThemeChanger.setThemes(R.style.Theme_Keychain_Light, R.style.Theme_Keychain_Dark);
+ mThemeChanger.changeTheme();
+ }
+
protected void setActionBarIcon(int iconRes) {
mToolbar.setNavigationIcon(iconRes);
}
@@ -85,9 +115,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);
@@ -102,6 +130,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 1d09b281f..972421abe 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
@@ -1,6 +1,8 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2013-2014 Signe Rüsch
+ * Copyright (C) 2013-2014 Philipp Jakubeit
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,7 +21,9 @@
package org.sufficientlysecure.keychain.ui.base;
import java.io.IOException;
+import java.math.BigInteger;
import java.nio.ByteBuffer;
+import java.security.interfaces.RSAPrivateCrtKey;
import android.app.Activity;
import android.app.PendingIntent;
@@ -27,18 +31,25 @@ 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;
import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.util.Arrays;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService.KeyNotFoundException;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
@@ -54,21 +65,135 @@ import org.sufficientlysecure.keychain.util.Preferences;
public abstract class BaseNfcActivity extends BaseActivity {
- public static final int REQUEST_CODE_PASSPHRASE = 1;
+ 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;
protected boolean mPw1ValidatedForSignature;
protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming?
+ 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)) {
@@ -77,25 +202,108 @@ 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 {
- handleNdefDiscoveredIntent(intent);
- } 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();
+
+ if (e instanceof TagLostException) {
+ onNfcError(getString(R.string.error_nfc_tag_lost));
+ return;
+ }
+
+ 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) {
+ int tries = status & 0x000F;
+ onNfcError(getResources().getQuantityString(R.plurals.error_pin, tries, tries));
+ return;
+ }
+
+ // Otherwise, all status codes are fixed values.
+ switch (status) {
+ // 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: {
+ onNfcError(getString(R.string.error_nfc_bad_data));
+ break;
+ }
+ case 0x6883: {
+ onNfcError(getString(R.string.error_nfc_chaining_error));
+ break;
+ }
+ case 0x6B00: {
+ onNfcError(getString(R.string.error_nfc_header, "P1/P2"));
+ break;
+ }
+ case 0x6D00: {
+ onNfcError(getString(R.string.error_nfc_header, "INS"));
+ break;
+ }
+ case 0x6E00: {
+ onNfcError(getString(R.string.error_nfc_header, "CLA"));
+ break;
+ }
+ // These error conditions are more likely to be experienced by an end user.
+ case 0x6285: {
+ onNfcError(getString(R.string.error_nfc_terminated));
+ break;
+ }
+ case 0x6700: {
+ onNfcError(getString(R.string.error_nfc_wrong_length));
+ break;
+ }
+ case 0x6982: {
+ onNfcError(getString(R.string.error_nfc_security_not_satisfied));
+ break;
+ }
+ case 0x6983: {
+ onNfcError(getString(R.string.error_nfc_authentication_blocked));
+ break;
+ }
+ case 0x6985: {
+ 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: {
+ 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: {
+ onNfcError(getString(R.string.error_nfc_unknown));
+ break;
+ }
+ default: {
+ onNfcError(getString(R.string.error_nfc, e.getMessage()));
+ break;
+ }
+ }
}
@@ -129,16 +337,29 @@ public abstract class BaseNfcActivity extends BaseActivity {
protected void obtainYubiKeyPin(RequiredInputParcel requiredInput) {
+ // shortcut if we only use the default yubikey pin
Preferences prefs = Preferences.getPreferences(this);
if (prefs.useDefaultYubiKeyPin()) {
mPin = new Passphrase("123456");
return;
}
- Intent intent = new Intent(this, PassphraseDialogActivity.class);
- intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
- RequiredInputParcel.createRequiredPassphrase(requiredInput));
- startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
+ try {
+ Passphrase phrase = PassphraseCacheService.getCachedPassphrase(this,
+ requiredInput.getMasterKeyId(), requiredInput.getSubKeyId());
+ if (phrase != null) {
+ mPin = phrase;
+ return;
+ }
+
+ Intent intent = new Intent(this, PassphraseDialogActivity.class);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
+ RequiredInputParcel.createRequiredPassphrase(requiredInput));
+ startActivityForResult(intent, REQUEST_CODE_PIN);
+ } catch (KeyNotFoundException e) {
+ throw new AssertionError(
+ "tried to find passphrase for non-existing key. this is a programming error!");
+ }
}
@@ -149,7 +370,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
- case REQUEST_CODE_PASSPHRASE:
+ case REQUEST_CODE_PIN: {
if (resultCode != Activity.RESULT_OK) {
setResult(resultCode);
finish();
@@ -158,6 +379,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
mPin = input.getPassphrase();
break;
+ }
default:
super.onActivityResult(requestCode, resultCode, data);
@@ -169,7 +391,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
* This method is called by onNewIntent above upon discovery of an NFC tag.
* It handles initialization and login to the application, subsequently
* calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then
- * finishes the activity with an appropiate result.
+ * finishes the activity with an appropriate result.
*
* On general communication, see also
* http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx
@@ -178,7 +400,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
* on ISO SmartCard Systems specification.
*
*/
- protected void handleNdefDiscoveredIntent(Intent intent) throws IOException {
+ protected void handleTagDiscoveredIntent(Intent intent) throws IOException {
Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
@@ -200,52 +422,23 @@ public abstract class BaseNfcActivity extends BaseActivity {
+ "06" // Lc (number of bytes)
+ "D27600012401" // Data (6 bytes)
+ "00"; // Le
- if ( ! nfcCommunicate(opening).endsWith(accepted)) { // activate connection
- throw new IOException("Initialization failed!");
+ String response = nfcCommunicate(opening); // activate connection
+ if ( ! response.endsWith(accepted) ) {
+ throw new CardException("Initialization failed!", parseCardStatus(response));
}
byte[] pwStatusBytes = nfcGetPwStatusBytes();
mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1);
mPw1ValidatedForSignature = false;
mPw1ValidatedForDecrypt = false;
+ mPw3Validated = false;
- 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 masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints);
-
- try {
- CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
- ring.getMasterKeyId();
-
- Intent intent = new Intent(
- BaseNfcActivity.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);
- finish();
- } catch (PgpKeyNotFoundException e) {
- Intent intent = new Intent(
- BaseNfcActivity.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);
- finish();
- }
-
+ public boolean isNfcConnected() {
+ return mIsoDep.isConnected();
}
/** Return the key id from application specific data stored on tag, or null
@@ -415,7 +608,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
if ( ! "9000".equals(status)) {
- throw new IOException("Bad NFC response code: " + status);
+ throw new CardException("Bad NFC response code: " + status, parseCardStatus(response));
}
// Make sure the signature we received is actually the expected number of bytes long!
@@ -460,13 +653,21 @@ public abstract class BaseNfcActivity extends BaseActivity {
return Hex.decode(decryptedSessionKey);
}
- /** Verifies the user's PW1 with the appropriate mode.
+ /** Verifies the user's PW1 or PW3 with the appropriate mode.
*
- * @param mode This is 0x81 for signing, 0x82 for everything else
+ * @param mode For PW1, this is 0x81 for signing, 0x82 for everything else.
+ * For PW3 (Admin PIN), mode is 0x83.
*/
public void nfcVerifyPIN(int mode) throws IOException {
- if (mPin != null) {
- byte[] pin = new String(mPin.getCharArray()).getBytes();
+ if (mPin != null || mode == 0x83) {
+
+ byte[] pin;
+ if (mode == 0x83) {
+ pin = mAdminPin.toStringUnsafe().getBytes();
+ } else {
+ pin = mPin.toStringUnsafe().getBytes();
+ }
+
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
// See specification, page 51
String accepted = "9000";
@@ -479,19 +680,231 @@ public abstract class BaseNfcActivity extends BaseActivity {
+ String.format("%02x", mode) // P2
+ String.format("%02x", pin.length) // Lc
+ Hex.toHexString(pin);
- if (!nfcCommunicate(login).equals(accepted)) { // login
+ String response = nfcCommunicate(login); // login
+ if (!response.equals(accepted)) {
handlePinError();
- throw new IOException("Bad PIN!");
+ throw new CardException("Bad PIN!", parseCardStatus(response));
}
if (mode == 0x81) {
mPw1ValidatedForSignature = true;
} else if (mode == 0x82) {
mPw1ValidatedForDecrypt = true;
+ } else if (mode == 0x83) {
+ mPw3Validated = true;
}
}
}
+ /** Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for
+ * conformance to the card's requirements for key length.
+ *
+ * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83.
+ * @param newPin The new PW1 or PW3.
+ */
+ 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();
+
+ if (pw == 0x81) {
+ if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) {
+ throw new IOException("Invalid PIN length");
+ }
+ } else if (pw == 0x83) {
+ if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) {
+ throw new IOException("Invalid PIN length");
+ }
+ } else {
+ throw new IOException("Invalid PW index for modify PIN operation");
+ }
+
+ byte[] pin;
+ if (pw == 0x83) {
+ pin = mAdminPin.toStringUnsafe().getBytes();
+ } else {
+ pin = mPin.toStringUnsafe().getBytes();
+ }
+
+ // Command APDU for CHANGE REFERENCE DATA command (page 32)
+ String changeReferenceDataApdu = "00" // CLA
+ + "24" // INS
+ + "00" // P1
+ + String.format("%02x", pw) // P2
+ + String.format("%02x", pin.length + newPin.length) // Lc
+ + getHex(pin)
+ + getHex(newPin);
+ String response = nfcCommunicate(changeReferenceDataApdu); // change PIN
+ if (!response.equals("9000")) {
+ handlePinError();
+ throw new CardException("Failed to change PIN", parseCardStatus(response));
+ }
+ }
+
+ /**
+ * Stores a data object on the card. Automatically validates the proper PIN for the operation.
+ * Supported for all data objects < 255 bytes in length. Only the cardholder certificate
+ * (0x7F21) can exceed this length.
+ *
+ * @param dataObject The data object to be stored.
+ * @param data The data to store in the object
+ */
+ public void nfcPutData(int dataObject, byte[] data) throws IOException {
+ if (data.length > 254) {
+ throw new IOException("Cannot PUT DATA with length > 254");
+ }
+ if (dataObject == 0x0101 || dataObject == 0x0103) {
+ if (!mPw1ValidatedForDecrypt) {
+ nfcVerifyPIN(0x82); // (Verify PW1 for non-signing operations)
+ }
+ } else if (!mPw3Validated) {
+ nfcVerifyPIN(0x83); // (Verify PW3)
+ }
+
+ String putDataApdu = "00" // CLA
+ + "DA" // INS
+ + String.format("%02x", (dataObject & 0xFF00) >> 8) // P1
+ + String.format("%02x", dataObject & 0xFF) // P2
+ + String.format("%02x", data.length) // Lc
+ + getHex(data);
+
+ String response = nfcCommunicate(putDataApdu); // put data
+ if (!response.equals("9000")) {
+ throw new CardException("Failed to put data.", parseCardStatus(response));
+ }
+ }
+
+ /**
+ * Puts a key on the card in the given slot.
+ *
+ * @param slot The slot on the card where the key should be stored:
+ * 0xB6: Signature Key
+ * 0xB8: Decipherment Key
+ * 0xA4: Authentication Key
+ */
+ public void nfcPutKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase)
+ throws IOException {
+ if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) {
+ throw new IOException("Invalid key slot");
+ }
+
+ RSAPrivateCrtKey crtSecretKey;
+ try {
+ secretKey.unlock(passphrase);
+ crtSecretKey = secretKey.getCrtSecretKey();
+ } catch (PgpGeneralException e) {
+ throw new IOException(e.getMessage());
+ }
+
+ // Shouldn't happen; the UI should block the user from getting an incompatible key this far.
+ if (crtSecretKey.getModulus().bitLength() > 2048) {
+ throw new IOException("Key too large to export to smart card.");
+ }
+
+ // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
+ if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
+ throw new IOException("Invalid public exponent for smart card key.");
+ }
+
+ if (!mPw3Validated) {
+ nfcVerifyPIN(0x83); // (Verify PW3 with mode 83)
+ }
+
+ byte[] header= Hex.decode(
+ "4D82" + "03A2" // Extended header list 4D82, length of 930 bytes. (page 23)
+ + String.format("%02x", slot) + "00" // CRT to indicate targeted key, no length
+ + "7F48" + "15" // Private key template 0x7F48, length 21 (decimal, 0x15 hex)
+ + "9103" // Public modulus, length 3
+ + "928180" // Prime P, length 128
+ + "938180" // Prime Q, length 128
+ + "948180" // Coefficient (1/q mod p), length 128
+ + "958180" // Prime exponent P (d mod (p - 1)), length 128
+ + "968180" // Prime exponent Q (d mod (1 - 1)), length 128
+ + "97820100" // Modulus, length 256, last item in private key template
+ + "5F48" + "820383");// DO 5F48; 899 bytes of concatenated key data will follow
+ byte[] dataToSend = new byte[934];
+ byte[] currentKeyObject;
+ int offset = 0;
+
+ System.arraycopy(header, 0, dataToSend, offset, header.length);
+ offset += header.length;
+ currentKeyObject = crtSecretKey.getPublicExponent().toByteArray();
+ System.arraycopy(currentKeyObject, 0, dataToSend, offset, 3);
+ offset += 3;
+ // NOTE: For a 2048-bit key, these lengths are fixed. However, bigint includes a leading 0
+ // in the array to represent sign, so we take care to set the offset to 1 if necessary.
+ currentKeyObject = crtSecretKey.getPrimeP().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
+ Arrays.fill(currentKeyObject, (byte)0);
+ offset += 128;
+ currentKeyObject = crtSecretKey.getPrimeQ().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
+ Arrays.fill(currentKeyObject, (byte)0);
+ offset += 128;
+ currentKeyObject = crtSecretKey.getCrtCoefficient().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
+ Arrays.fill(currentKeyObject, (byte)0);
+ offset += 128;
+ currentKeyObject = crtSecretKey.getPrimeExponentP().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
+ Arrays.fill(currentKeyObject, (byte)0);
+ offset += 128;
+ currentKeyObject = crtSecretKey.getPrimeExponentQ().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
+ Arrays.fill(currentKeyObject, (byte)0);
+ offset += 128;
+ currentKeyObject = crtSecretKey.getModulus().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256);
+
+ String putKeyCommand = "10DB3FFF";
+ String lastPutKeyCommand = "00DB3FFF";
+
+ // Now we're ready to communicate with the card.
+ offset = 0;
+ String response;
+ while(offset < dataToSend.length) {
+ int dataRemaining = dataToSend.length - offset;
+ if (dataRemaining > 254) {
+ response = nfcCommunicate(
+ putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254)
+ );
+ offset += 254;
+ } else {
+ int length = dataToSend.length - offset;
+ response = nfcCommunicate(
+ lastPutKeyCommand + String.format("%02x", length)
+ + Hex.toHexString(dataToSend, offset, length));
+ offset += length;
+ }
+
+ if (!response.endsWith("9000")) {
+ throw new CardException("Key export to card failed", parseCardStatus(response));
+ }
+ }
+
+ // Clear array with secret data before we return.
+ Arrays.fill(dataToSend, (byte) 0);
+ }
+
+ /**
+ * Parses out the status word from a JavaCard response string.
+ *
+ * @param response A hex string with the response from the card
+ * @return A short indicating the SW1/SW2, or 0 if a status could not be determined.
+ */
+ short parseCardStatus(String response) {
+ if (response.length() < 4) {
+ return 0; // invalid input
+ }
+
+ try {
+ return Short.parseShort(response.substring(response.length() - 4), 16);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
/**
* Prints a message to the screen
*
@@ -561,4 +974,18 @@ public abstract class BaseNfcActivity extends BaseActivity {
return new String(Hex.encode(raw));
}
+ public class CardException extends IOException {
+ private short mResponseCode;
+
+ public CardException(String detailMessage, short responseCode) {
+ super(detailMessage);
+ mResponseCode = responseCode;
+ }
+
+ public short getResponseCode() {
+ return mResponseCode;
+ }
+
+ }
+
}
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
new file mode 100644
index 000000000..321af0df5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java
@@ -0,0 +1,59 @@
+package org.sufficientlysecure.keychain.ui.base;
+
+
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
+
+
+public abstract class CachingCryptoOperationFragment <T extends Parcelable, S extends OperationResult>
+ extends QueueingCryptoOperationFragment<T, S> {
+
+ public static final String ARG_CACHED_ACTIONS = "cached_actions";
+
+ private T mCachedActionsParcel;
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putParcelable(ARG_CACHED_ACTIONS, mCachedActionsParcel);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ mCachedActionsParcel = savedInstanceState.getParcelable(ARG_CACHED_ACTIONS);
+ }
+ }
+
+ @Override
+ public void onQueuedOperationSuccess(S result) {
+ mCachedActionsParcel = null;
+ }
+
+ @Override
+ public void onQueuedOperationError(S result) {
+ super.onQueuedOperationError(result);
+ mCachedActionsParcel = null;
+ }
+
+ @Override
+ public abstract T createOperationInput();
+
+ protected T getCachedActionsParcel() {
+ return mCachedActionsParcel;
+ }
+
+ protected void cacheActionsParcel(T cachedActionsParcel) {
+ mCachedActionsParcel = cachedActionsParcel;
+ }
+
+ public void onCryptoOperationCancelled() {
+ 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 232a39f86..de90d48fd 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,114 +18,116 @@
package org.sufficientlysecure.keychain.ui.base;
-import android.app.Activity;
+
+import android.content.Context;
import android.content.Intent;
-import android.os.Bundle;
-import android.os.Message;
+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.operations.results.InputPendingResult;
+import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.service.KeychainService;
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.PassphraseDialogActivity;
-/**
- * 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 extends Fragment {
-
- public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
- public static final int REQUEST_CODE_NFC = 0x00008002;
-
- private void initiateInputActivity(RequiredInputParcel requiredInput) {
-
- switch (requiredInput.mType) {
- case NFC_DECRYPT:
- case NFC_SIGN: {
- Intent intent = new Intent(getActivity(), NfcOperationActivity.class);
- intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
- startActivityForResult(intent, REQUEST_CODE_NFC);
- return;
- }
-
- case PASSPHRASE:
- case PASSPHRASE_SYMMETRIC: {
- Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
- intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
- startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
- return;
- }
- }
+public abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult>
+ extends Fragment implements CryptoOperationHelper.Callback<T, S> {
- throw new RuntimeException("Unhandled pending result!");
+ final private CryptoOperationHelper<T, S> mOperationHelper;
+
+ public CryptoOperationFragment() {
+ mOperationHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_processing);
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode == Activity.RESULT_CANCELED) {
- onCryptoOperationCancelled();
- return;
- }
+ public CryptoOperationFragment(Integer initialProgressMsg) {
+ mOperationHelper = new CryptoOperationHelper<>(1, this, this, initialProgressMsg);
+ }
- switch (requestCode) {
- case REQUEST_CODE_PASSPHRASE: {
- if (resultCode == Activity.RESULT_OK && data != null) {
- CryptoInputParcel cryptoInput =
- data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
- cryptoOperation(cryptoInput);
- return;
- }
- break;
- }
-
- case REQUEST_CODE_NFC: {
- if (resultCode == Activity.RESULT_OK && data != null) {
- CryptoInputParcel cryptoInput =
- data.getParcelableExtra(NfcOperationActivity.RESULT_DATA);
- cryptoOperation(cryptoInput);
- return;
- }
- break;
- }
-
- default: {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
+ public CryptoOperationFragment(int id, Integer initialProgressMsg) {
+ mOperationHelper = new CryptoOperationHelper<>(id, this, this, initialProgressMsg);
}
- public boolean handlePendingMessage(Message message) {
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mOperationHelper.handleActivityResult(requestCode, resultCode, data);
+ }
- if (message.arg1 == ServiceProgressHandler.MessageStatus.OKAY.ordinal()) {
- Bundle data = message.getData();
+ /** 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() {
+ mOperationHelper.cryptoOperation();
+ }
- OperationResult result = data.getParcelable(OperationResult.EXTRA_RESULT);
- if (result == null || !(result instanceof InputPendingResult)) {
- return false;
- }
+ protected void cryptoOperation(CryptoInputParcel cryptoInput) {
+ mOperationHelper.cryptoOperation(cryptoInput);
+ }
- InputPendingResult pendingResult = (InputPendingResult) result;
- if (pendingResult.isPending()) {
- RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel();
- initiateInputActivity(requiredInput);
- return true;
- }
- }
+ @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;
}
- protected void cryptoOperation() {
- cryptoOperation(new CryptoInputParcel());
+ public void setProgressMessageResource(int id) {
+ mOperationHelper.setProgressMessageResource(id);
+ }
+
+ @Override
+ /** 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
+ abstract public void onCryptoOperationError(S result);
+
+ @Override
+ public void onCryptoOperationCancelled() {
}
- protected abstract void cryptoOperation(CryptoInputParcel cryptoInput);
+ 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;
- protected void onCryptoOperationCancelled() {
- // Nothing to do here, in most cases
+ 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
new file mode 100644
index 000000000..6d7bf4cd0
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 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.base;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+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.operations.results.InputPendingResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.service.KeychainService;
+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;
+
+/**
+ * Designed to be integrated into activities or fragments used for CryptoOperations.
+ * Encapsulates the execution of a crypto operation and handling of input pending cases.s
+ *
+ * @param <T> The type of input parcel sent to the operation
+ * @param <S> The type of result returned by the operation
+ */
+public class CryptoOperationHelper<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);
+ }
+
+ // 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 helper-id code. the first two
+ // summands are stored in the mHelperId for easy operation.
+ private final int mHelperId;
+ // bitmask for helperId is everything except the least 8 bits
+ public static final int HELPER_ID_BITMASK = ~0xff;
+
+ 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 Integer mProgressMessageResource;
+
+ private FragmentActivity mActivity;
+ private Fragment mFragment;
+ private Callback<T, S> mCallback;
+
+ private boolean mUseFragment; // short hand for mActivity == null
+
+ /**
+ * If OperationHelper is being integrated into an activity
+ */
+ public CryptoOperationHelper(int id, FragmentActivity activity, Callback<T, S> callback,
+ Integer progressMessageString) {
+ mHelperId = (id << 9) + (1<<8);
+ mActivity = activity;
+ mUseFragment = false;
+ mCallback = callback;
+ mProgressMessageResource = progressMessageString;
+ }
+
+ /**
+ * if OperationHelper is being integrated into a fragment
+ */
+ public CryptoOperationHelper(int id, Fragment fragment, Callback<T, S> callback, Integer progressMessageString) {
+ mHelperId = (id << 9) + (1<<8);
+ mFragment = fragment;
+ mUseFragment = true;
+ mProgressMessageResource = progressMessageString;
+ mCallback = callback;
+ }
+
+ public void setProgressMessageResource(int id) {
+ mProgressMessageResource = id;
+ }
+
+ 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);
+ intent.putExtra(NfcOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
+ startActivityForResult(intent, REQUEST_CODE_NFC);
+ return;
+ }
+
+ case PASSPHRASE:
+ case PASSPHRASE_SYMMETRIC: {
+ Intent intent = new Intent(activity, PassphraseDialogActivity.class);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
+ 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!");
+ }
+ }
+ }
+
+ protected void startActivityForResult(Intent intent, int requestCode) {
+ if (mUseFragment) {
+ mFragment.startActivityForResult(intent, mHelperId + requestCode);
+ } else {
+ mActivity.startActivityForResult(intent, mHelperId + requestCode);
+ }
+ }
+
+ /**
+ * 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 ((requestCode & HELPER_ID_BITMASK) != mHelperId) {
+ // this wasn't meant for us to handle
+ return false;
+ }
+ Log.d(Constants.TAG, "handling activity result in OperationHelper");
+ // filter out mHelperId from requestCode
+ requestCode ^= mHelperId;
+
+ if (resultCode == Activity.RESULT_CANCELED) {
+ mCallback.onCryptoOperationCancelled();
+ return true;
+ }
+
+ switch (requestCode) {
+ case REQUEST_CODE_PASSPHRASE: {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ CryptoInputParcel cryptoInput =
+ data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
+ cryptoOperation(cryptoInput);
+ }
+ break;
+ }
+
+ case REQUEST_CODE_NFC: {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ CryptoInputParcel cryptoInput =
+ data.getParcelableExtra(NfcOperationActivity.RESULT_CRYPTO_INPUT);
+ cryptoOperation(cryptoInput);
+ }
+ break;
+ }
+
+ 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;
+ }
+
+ protected void dismissProgress() {
+ FragmentManager fragmentManager =
+ mUseFragment ? mFragment.getFragmentManager() :
+ mActivity.getSupportFragmentManager();
+
+ if (fragmentManager == null) { // the fragment holding us has died
+ // fragmentManager was null when used with DialogFragments. (they close on click?)
+ return;
+ }
+
+ ProgressDialogFragment progressDialogFragment =
+ (ProgressDialogFragment) fragmentManager.findFragmentByTag(
+ ServiceProgressHandler.TAG_PROGRESS_DIALOG);
+
+ if (progressDialogFragment == null) {
+ return;
+ }
+
+ progressDialogFragment.dismissAllowingStateLoss();
+
+ }
+
+ public void cryptoOperation(final CryptoInputParcel cryptoInput) {
+
+ FragmentActivity activity = mUseFragment ? mFragment.getActivity() : mActivity;
+
+ T operationInput = mCallback.createOperationInput();
+ if (operationInput == null) {
+ return;
+ }
+
+ // Send all information needed to service to edit key in other thread
+ Intent intent = new Intent(activity, KeychainService.class);
+
+ intent.putExtra(KeychainService.EXTRA_OPERATION_INPUT, operationInput);
+ intent.putExtra(KeychainService.EXTRA_CRYPTO_INPUT, cryptoInput);
+
+ ServiceProgressHandler saveHandler = new ServiceProgressHandler(activity) {
+ @Override
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == MessageStatus.OKAY.ordinal()) {
+
+ // get returned data bundle
+ Bundle returnData = message.getData();
+ if (returnData == null) {
+ return;
+ }
+
+ final OperationResult result =
+ returnData.getParcelable(OperationResult.EXTRA_RESULT);
+
+ onHandleResult(result);
+ }
+ }
+
+ @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)) {
+ super.onSetProgress(msg, progress, max);
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
+
+ if (mProgressMessageResource != null) {
+ saveHandler.showProgressDialog(
+ activity.getString(mProgressMessageResource),
+ ProgressDialog.STYLE_HORIZONTAL, false);
+ }
+
+ activity.startService(intent);
+ }
+
+ public void cryptoOperation() {
+ cryptoOperation(new CryptoInputParcel());
+ }
+
+ 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, pendingResult.mCryptoInputParcel);
+ return;
+ }
+ }
+
+ dismissProgress();
+
+ try {
+ 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 cbef5950f..47bc7dfda 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
@@ -17,8 +17,14 @@
package org.sufficientlysecure.keychain.ui.dialog;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+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;
@@ -29,8 +35,8 @@ 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.test.suitebuilder.TestSuiteBuilder;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -42,23 +48,24 @@ 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;
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-
-import javax.net.ssl.HttpsURLConnection;
+import java.net.Proxy;
-public class AddKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener {
- private static final String ARG_MESSENGER = "messenger";
- private static final String ARG_TITLE = "title";
+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;
@@ -66,50 +73,54 @@ 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 static enum FailureReason {
+ public enum DialogAction {
+ ADD,
+ EDIT
+ }
+
+ public enum FailureReason {
INVALID_URL,
CONNECTION_FAILED
}
- ;
-
- /**
- * Creates new instance of this dialog fragment
- *
- * @param title title of dialog
- * @param messenger to communicate back after setting the passphrase
- * @return
- */
- public static AddKeyserverDialogFragment newInstance(Messenger messenger, int title) {
- 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.putInt(ARG_TITLE, title);
args.putParcelable(ARG_MESSENGER, messenger);
+ args.putSerializable(ARG_ACTION, action);
+ args.putString(ARG_KEYSERVER, keyserver);
+ args.putInt(ARG_POSITION, position);
frag.setArguments(args);
return frag;
}
- /**
- * Creates dialog
- */
+ @NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
- int title = getArguments().getInt(ARG_TITLE);
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
+ mDialogAction = (DialogAction) getArguments().getSerializable(ARG_ACTION);
+ mPosition = getArguments().getInt(ARG_POSITION);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
- alert.setTitle(title);
-
LayoutInflater inflater = activity.getLayoutInflater();
View view = inflater.inflate(R.layout.add_keyserver_dialog, null);
alert.setView(view);
@@ -117,14 +128,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
}
});
@@ -136,6 +159,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/
@@ -172,25 +212,63 @@ 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();
+ OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() {
+ @Override
+ public void onOrbotStarted() {
+ verifyConnection(keyserverUrl,
+ proxyPrefs.parcelableProxy.getProxy());
+ }
+
+ @Override
+ public void onNeutralButton() {
+ verifyConnection(keyserverUrl, null);
+ }
+
+ @Override
+ public void onCancel() {
+ // do nothing
+ }
+ };
+
+ if (OrbotHelper.putOrbotInRequiredState(dialogActions, 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);
}
@@ -201,7 +279,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;
@@ -225,19 +303,24 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
String scheme = keyserverUri.getScheme();
String schemeSpecificPart = keyserverUri.getSchemeSpecificPart();
String fragment = keyserverUri.getFragment();
- if (scheme == null) throw new MalformedURLException();
- if (scheme.equalsIgnoreCase("hkps")) scheme = "https";
- else if (scheme.equalsIgnoreCase("hkp")) scheme = "http";
+ if (scheme == null) {
+ throw new MalformedURLException();
+ }
+ if ("hkps".equalsIgnoreCase(scheme)) {
+ scheme = "https";
+ } else if ("hkp".equalsIgnoreCase(scheme)) {
+ scheme = "http";
+ }
URI newKeyserver = new URI(scheme, schemeSpecificPart, fragment);
Log.d("Converted URL", newKeyserver.toString());
- TlsHelper.openConnection(newKeyserver.toURL()).getInputStream();
+
+ 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 e) {
- Log.w(Constants.TAG, "Invalid keyserver URL entered by user.");
- reason = FailureReason.INVALID_URL;
- } catch (URISyntaxException e) {
+ } catch (MalformedURLException | URISyntaxException e) {
Log.w(Constants.TAG, "Invalid keyserver URL entered by user.");
reason = FailureReason.INVALID_URL;
} catch (IOException e) {
@@ -251,7 +334,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 581a96e52..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java
+++ /dev/null
@@ -1,190 +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.app.ProgressDialog;
-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.pgp.KeyRing;
-import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.util.Log;
-
-import java.util.HashMap;
-
-public class DeleteKeyDialogFragment extends DialogFragment {
- 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;
-
- /**
- * 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 Dialog onCreateDialog(Bundle savedInstanceState) {
- final FragmentActivity activity = getActivity();
- final Messenger messenger = 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) {
-
- // Send all information needed to service to import key in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
-
- intent.setAction(KeychainIntentService.ACTION_DELETE);
-
- // Message is received after importing is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_deleting),
- ProgressDialog.STYLE_HORIZONTAL,
- true,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- @Override
- public void handleMessage(Message message) {
- super.handleMessage(message);
- // handle messages by standard KeychainIntentServiceHandler first
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- try {
- Message msg = Message.obtain();
- msg.copyFrom(message);
- messenger.send(msg);
- } catch (RemoteException e) {
- Log.e(Constants.TAG, "messenger error", e);
- }
- }
- }
- };
-
- // fill values for this action
- Bundle data = new Bundle();
- data.putLongArray(KeychainIntentService.DELETE_KEY_LIST, masterKeyIds);
- data.putBoolean(KeychainIntentService.DELETE_IS_SECRET, hasSecret);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- saveHandler.showProgressDialog(getActivity());
-
- // start service with intent
- getActivity().startService(intent);
-
- dismiss();
- }
- });
- builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dismiss();
- }
- });
-
- return builder.show();
- }
-
-}
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 9568312f5..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,6 +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_MOVE_KEY_TO_CARD = 4;
private Messenger mMessenger;
@@ -76,6 +77,9 @@ public class EditSubkeyDialogFragment extends DialogFragment {
case 2:
sendMessageToHandler(MESSAGE_STRIP, null);
break;
+ case 3:
+ 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..84774ae5e 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
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
@@ -119,14 +120,19 @@ public class FileDialogFragment extends DialogFragment {
mFilename = (EditText) view.findViewById(R.id.input);
mFilename.setText(mFile.getName());
mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
- mBrowse.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- // only .asc or .gpg files
- // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
- // or gpg types!
- FileHelper.openFile(FileDialogFragment.this, Uri.fromFile(mFile), "*/*", REQUEST_CODE);
- }
- });
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ mBrowse.setVisibility(View.GONE);
+ } else {
+ mBrowse.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ // only .asc or .gpg files
+ // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
+ // or gpg types!
+ FileHelper.saveDocumentKitKat(
+ FileDialogFragment.this, "*/*", mFile.getName(), REQUEST_CODE);
+ }
+ });
+ }
mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
if (checkboxText == null) {
@@ -136,6 +142,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..b06e05c30
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java
@@ -0,0 +1,163 @@
+/*
+ * 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.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.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AlertDialog;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.widget.Button;
+
+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";
+
+ private static final int ORBOT_REQUEST_CODE = 1;
+
+ public static final int MESSAGE_MIDDLE_BUTTON = 1;
+ public static final int MESSAGE_DIALOG_CANCELLED = 2; // for either cancel or enable pressed
+ public static final int MESSAGE_ORBOT_STARTED = 3; // 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;
+ }
+
+ @NonNull
+ @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_CANCELLED;
+ 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);
+ }
+ }
+ });
+
+ builder.setPositiveButton(R.string.orbot_start_dialog_start, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // actual onClick defined in onStart, this is just to make the button appear
+ }
+ });
+
+ return builder.show();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ //super.onStart() is where dialog.show() is actually called on the underlying dialog,
+ // so we have to do it after this point
+ AlertDialog d = (AlertDialog) getDialog();
+ if (d != null) {
+ Button positiveButton = d.getButton(Dialog.BUTTON_POSITIVE);
+ positiveButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ startActivityForResult(OrbotHelper.getShowOrbotStartIntent(),
+ ORBOT_REQUEST_CODE);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == ORBOT_REQUEST_CODE) {
+ // assume Orbot was started
+ final Messenger messenger = getArguments().getParcelable(ARG_MESSENGER);
+
+ Message msg = Message.obtain();
+ msg.what = MESSAGE_ORBOT_STARTED;
+ 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);
+ }
+ dismiss();
+ }
+ }
+}
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 af9d175ff..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
@@ -25,6 +25,7 @@ import android.content.DialogInterface.OnKeyListener;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
@@ -32,25 +33,19 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
-import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.service.CloudImportService;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.service.KeychainService;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
+/**
+ * meant to be used
+ */
public class ProgressDialogFragment extends DialogFragment {
private static final String ARG_MESSAGE = "message";
private static final String ARG_STYLE = "style";
private static final String ARG_CANCELABLE = "cancelable";
private static final String ARG_SERVICE_TYPE = "service_class";
- public static enum ServiceType {
- KEYCHAIN_INTENT,
- CLOUD_IMPORT
- }
-
- ServiceType mServiceType;
-
boolean mCanCancel = false, mPreventCancel = false, mIsCancelled = false;
/**
@@ -58,41 +53,35 @@ public class ProgressDialogFragment extends DialogFragment {
* @param message the message to be displayed initially above the progress bar
* @param style the progress bar style, as defined in ProgressDialog (horizontal or spinner)
* @param cancelable should we let the user cancel this operation
- * @param serviceType which Service this progress dialog is meant for
* @return
*/
- public static ProgressDialogFragment newInstance(String message, int style, boolean cancelable,
- ServiceType serviceType) {
+ public static ProgressDialogFragment newInstance(String message, int style, boolean cancelable) {
ProgressDialogFragment frag = new ProgressDialogFragment();
Bundle args = new Bundle();
args.putString(ARG_MESSAGE, message);
args.putInt(ARG_STYLE, style);
args.putBoolean(ARG_CANCELABLE, cancelable);
- args.putSerializable(ARG_SERVICE_TYPE, serviceType);
frag.setArguments(args);
return frag;
}
- /** Updates progress of dialog */
public void setProgress(int messageId, int progress, int max) {
setProgress(getString(messageId), progress, max);
}
- /** Updates progress of dialog */
public void setProgress(int progress, int max) {
- if (mIsCancelled) {
+ ProgressDialog dialog = (ProgressDialog) getDialog();
+
+ if (mIsCancelled || dialog == null) {
return;
}
- ProgressDialog dialog = (ProgressDialog) getDialog();
-
dialog.setProgress(progress);
dialog.setMax(max);
}
- /** Updates progress of dialog */
public void setProgress(String message, int progress, int max) {
ProgressDialog dialog = (ProgressDialog) getDialog();
@@ -105,17 +94,12 @@ public class ProgressDialogFragment extends DialogFragment {
dialog.setMax(max);
}
- /**
- * Creates dialog
- */
+ @NonNull
@Override
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);
@@ -126,7 +110,6 @@ public class ProgressDialogFragment extends DialogFragment {
String message = getArguments().getString(ARG_MESSAGE);
int style = getArguments().getInt(ARG_STYLE);
mCanCancel = getArguments().getBoolean(ARG_CANCELABLE);
- mServiceType = (ServiceType) getArguments().getSerializable(ARG_SERVICE_TYPE);
dialog.setMessage(message);
dialog.setProgressStyle(style);
@@ -165,7 +148,12 @@ public class ProgressDialogFragment extends DialogFragment {
}
mPreventCancel = preventCancel;
- final Button negative = ((ProgressDialog) getDialog()).getButton(DialogInterface.BUTTON_NEGATIVE);
+ ProgressDialog dialog = (ProgressDialog) getDialog();
+ if (dialog == null) {
+ return;
+ }
+
+ final Button negative = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
negative.setEnabled(mIsCancelled && !preventCancel);
}
@@ -188,30 +176,24 @@ public class ProgressDialogFragment extends DialogFragment {
negative.setClickable(false);
negative.setTextColor(Color.GRAY);
+ // send a cancel message. note that this message will be handled by
+ // KeychainService.onStartCommand, which runs in this thread,
+ // not the service one, and will not queue up a command.
+ Intent serviceIntent = new Intent(getActivity(), KeychainService.class);
+
+ serviceIntent.setAction(KeychainService.ACTION_CANCEL);
+ getActivity().startService(serviceIntent);
+
// Set the progress bar accordingly
ProgressDialog dialog = (ProgressDialog) getDialog();
+ if (dialog == null) {
+ return;
+ }
+
dialog.setIndeterminate(true);
dialog.setMessage(getString(R.string.progress_cancelling));
- // send a cancel message. note that this message will be handled by
- // KeychainIntentService.onStartCommand, which runs in this thread,
- // not the service one, and will not queue up a command.
- Intent serviceIntent = null;
-
- switch (mServiceType) {
- case CLOUD_IMPORT:
- serviceIntent = new Intent(getActivity(), CloudImportService.class);
- break;
- case KEYCHAIN_INTENT:
- serviceIntent = new Intent(getActivity(), KeychainIntentService.class);
- break;
- default:
- //should never happen, unless we forget to include a ServiceType enum case
- Log.e(Constants.TAG, "Unrecognized ServiceType at ProgressDialogFragment");
- }
- serviceIntent.setAction(KeychainIntentService.ACTION_CANCEL);
- getActivity().startService(serviceIntent);
}
});
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..82d1be4ed
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java
@@ -0,0 +1,78 @@
+/*
+ * 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.annotation.NonNull;
+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 xml resource for title of the install dialog
+ * @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 xml string resource for title of the dialog
+ * @param message xml string resource to display as dialog body
+ * @param packageToInstall name of package to install
+ * @return
+ */
+ public static SupportInstallDialogFragment newInstance(int title, int message,
+ String packageToInstall) {
+ return newInstance(null, title, message, packageToInstall, -1, false);
+ }
+
+ @NonNull
+ @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/linked/LinkedIdCreateDnsStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java
index 8062428e3..c54d0c948 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java
@@ -107,10 +107,10 @@ public class LinkedIdCreateDnsStep1Fragment extends Fragment {
if (uri.length() > 0) {
if (checkUri(uri)) {
mEditDns.setCompoundDrawablesWithIntrinsicBounds(0, 0,
- R.drawable.uid_mail_ok, 0);
+ R.drawable.ic_stat_retyped_ok, 0);
} else {
mEditDns.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/linked/LinkedIdCreateDnsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java
index e0e6976ee..c9eca8882 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java
@@ -17,6 +17,14 @@
package org.sufficientlysecure.keychain.ui.linked;
+
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -26,17 +34,14 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
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.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
import org.sufficientlysecure.keychain.linked.resources.DnsResource;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
-import java.io.FileNotFoundException;
-import java.io.PrintWriter;
-
public class LinkedIdCreateDnsStep2Fragment extends LinkedIdCreateFinalFragment {
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
@@ -117,7 +122,20 @@ public class LinkedIdCreateDnsStep2Fragment extends LinkedIdCreateFinalFragment
}
private void proofToClipboard() {
- ClipboardReflection.copyToClipboard(getActivity(), mResourceString);
+ 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, mResourceString);
+ clipMan.setPrimaryClip(clip);
+
Notify.create(getActivity(), R.string.linked_text_clipboard, Notify.Style.OK).show();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java
index eedc7cdd9..24499a467 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java
@@ -1,12 +1,11 @@
package org.sufficientlysecure.keychain.ui.linked;
-import android.app.ProgressDialog;
-import android.content.Intent;
+
import android.graphics.PorterDuff;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@@ -16,20 +15,18 @@ import android.widget.TextView;
import android.widget.ViewAnimator;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.linked.LinkedAttribute;
+import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
-import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
-import org.sufficientlysecure.keychain.linked.LinkedAttribute;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment.ServiceType;
import org.sufficientlysecure.keychain.ui.util.Notify;
+
public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragment {
protected LinkedIdWizard mLinkedIdWizard;
@@ -169,54 +166,31 @@ public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragmen
}
- protected void cryptoOperation(CryptoInputParcel cryptoInput) {
-
+ @Override
+ protected void cryptoOperation() {
if (mVerifiedResource == null) {
Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
.show(LinkedIdCreateFinalFragment.this);
return;
}
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_saving),
- ProgressDialog.STYLE_HORIZONTAL,
- true, ServiceType.KEYCHAIN_INTENT) {
-
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- // handle pending messages
- if (handlePendingMessage(message)) {
- return;
- }
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
-
- // get returned data bundle
- Bundle returnData = message.getData();
- if (returnData == null) {
- return;
- }
- final OperationResult result =
- returnData.getParcelable(OperationResult.EXTRA_RESULT);
- if (result == null) {
- return;
- }
-
- // if bad -> display here!
- if (!result.success()) {
- result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this);
- return;
- }
+ super.cryptoOperation();
+ }
- getActivity().finish();
+ @Override
+ protected void cryptoOperation(CryptoInputParcel cryptoInput) {
+ if (mVerifiedResource == null) {
+ Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
+ .show(LinkedIdCreateFinalFragment.this);
+ return;
+ }
- }
- }
- };
+ super.cryptoOperation(cryptoInput);
+ }
+ @Nullable
+ @Override
+ public Parcelable createOperationInput() {
SaveKeyringParcel skp =
new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint);
@@ -225,25 +199,22 @@ public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragmen
skp.mAddUserAttribute.add(ua);
- // Send all information needed to service to import key in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_EDIT_KEYRING);
-
- // fill values for this action
- Bundle data = new Bundle();
- data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
- data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, skp);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+ return skp;
+ }
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+ @Override
+ public void onCryptoOperationSuccess(OperationResult result) {
+ // if bad -> display here!
+ if (!result.success()) {
+ result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this);
+ return;
+ }
- // show progress dialog
- saveHandler.showProgressDialog(getActivity());
+ getActivity().finish();
+ }
- // start service with intent
- getActivity().startService(intent);
+ @Override
+ public void onCryptoOperationError(OperationResult result) {
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java
index 7bc33c93b..8a05c35db 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java
@@ -103,10 +103,10 @@ public class LinkedIdCreateHttpsStep1Fragment extends Fragment {
if (uri.length() > 0) {
if (checkUri(uri)) {
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
- R.drawable.uid_mail_ok, 0);
+ R.drawable.ic_stat_retyped_ok, 0);
} else {
mEditUri.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/linked/LinkedIdCreateHttpsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java
index 2af97fe36..22a201ba3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java
@@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui.linked;
import android.content.Intent;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.view.LayoutInflater;
@@ -135,13 +134,10 @@ public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragmen
String targetName = "pgpkey.txt";
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- File targetFile = new File(Constants.Path.APP_DIR, targetName);
- FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file),
- getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT);
- } else {
- FileHelper.saveDocument(this, "text/plain", targetName, REQUEST_CODE_OUTPUT);
- }
+ FileHelper.saveDocument(this,
+ targetName, Uri.fromFile(new File(Constants.Path.APP_DIR, targetName)),
+ "text/plain", R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to,
+ REQUEST_CODE_OUTPUT);
}
private void saveFile(Uri uri) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java
index 82aaa51c4..7007fa50c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java
@@ -12,8 +12,8 @@ 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.os.Parcelable;
+import android.support.annotation.Nullable;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.support.v4.app.LoaderManager;
@@ -31,20 +31,17 @@ import android.widget.ViewAnimator;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.key;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
import org.sufficientlysecure.keychain.linked.LinkedResource;
import org.sufficientlysecure.keychain.linked.UriAttribute;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
@@ -58,7 +55,6 @@ import org.sufficientlysecure.keychain.ui.widget.CertListWidget;
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
import org.sufficientlysecure.keychain.util.Log;
-
public class LinkedIdViewFragment extends CryptoOperationFragment implements
LoaderManager.LoaderCallbacks<Cursor>, OnBackStackChangedListener {
@@ -97,6 +93,12 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements
return frag;
}
+ public LinkedIdViewFragment() {
+ // IMPORTANT: the id must be unique in the ViewKeyActivity CryptoOperationHelper id namespace!
+ // no initial progress message -> we handle progress ourselves!
+ super(5, null);
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -499,7 +501,7 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements
}
// get the user's passphrase for this key (if required)
- mCertifyKeyId = mViewHolder.vKeySpinner.getSelectedItemId();
+ mCertifyKeyId = mViewHolder.vKeySpinner.getSelectedKeyId();
if (mCertifyKeyId == key.none || mCertifyKeyId == key.symmetric) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
SubtleAttentionSeeker.tintBackground(mViewHolder.vKeySpinnerContainer, 600).start();
@@ -509,11 +511,13 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements
return;
}
+ mViewHolder.setVerifyingState(mContext, VerifyState.CERTIFYING, false);
cryptoOperation();
+
}
@Override
- protected void onCryptoOperationCancelled() {
+ public void onCryptoOperationCancelled() {
super.onCryptoOperationCancelled();
// go back to 'verified ok'
@@ -521,68 +525,34 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements
}
+ @Nullable
@Override
- protected void cryptoOperation(CryptoInputParcel cryptoInput) {
-
- if (mIsSecret) {
- return;
- }
-
- mViewHolder.setVerifyingState(mContext, VerifyState.CERTIFYING, false);
-
- Bundle data = new Bundle();
- {
-
- long masterKeyId = KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint);
- CertifyAction action = new CertifyAction(masterKeyId, null,
- Collections.singletonList(mLinkedId.toUserAttribute()));
-
- // fill values for this action
- CertifyActionsParcel parcel = new CertifyActionsParcel(mCertifyKeyId);
- parcel.mCertifyActions.addAll(Collections.singletonList(action));
- data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
-
- data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
+ public Parcelable createOperationInput() {
+ long masterKeyId = KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint);
+ CertifyAction action = new CertifyAction(masterKeyId, null,
+ Collections.singletonList(mLinkedId.toUserAttribute()));
- /* if (mUploadKeyCheckbox.isChecked()) {
- String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
- data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
- } */
- }
-
- // Send all information needed to service to sign key in other thread
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING);
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Message is received after signing is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- // handle pending messages
- if (handlePendingMessage(message)) {
- return;
- }
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- Bundle data = message.getData();
- CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
- result.createNotify(getActivity()).show();
- // no need to do anything else, we will get a loader refresh!
- }
+ // fill values for this action
+ CertifyActionsParcel parcel = new CertifyActionsParcel(mCertifyKeyId);
+ parcel.mCertifyActions.addAll(Collections.singletonList(action));
- }
- };
+ return parcel;
+ }
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+ @Override
+ public void onCryptoOperationSuccess(OperationResult result) {
+ result.createNotify(getActivity()).show();
+ // no need to do anything else, we will get a loader refresh!
+ }
- // start service with intent
- getActivity().startService(intent);
+ @Override
+ public void onCryptoOperationError(OperationResult result) {
+ result.createNotify(getActivity()).show();
+ }
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return true;
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java
new file mode 100644
index 000000000..43ccac24f
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java
@@ -0,0 +1,126 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+
+import org.spongycastle.util.Arrays;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.BitSet;
+
+public class ExperimentalWordConfirm {
+
+ public static String getWords(Context context, byte[] fingerprintBlob) {
+ ArrayList<String> words = new ArrayList<>();
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(
+ context.getAssets().open("word_confirm_list.txt"),
+ "UTF-8"
+ ));
+
+ String line = reader.readLine();
+ while (line != null) {
+ words.add(line);
+
+ line = reader.readLine();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("IOException", e);
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ String fingerprint = "";
+
+ // NOTE: 160 bit SHA-1 truncated to 156 bit
+ byte[] fingerprintBlobTruncated = Arrays.copyOfRange(fingerprintBlob, 0, 156 / 8);
+
+ // TODO: implement key stretching to minimize fp length?
+
+ // BitSet bits = BitSet.valueOf(fingerprintBlob); // min API 19 and little endian!
+ BitSet bits = bitSetToByteArray(fingerprintBlobTruncated);
+ Log.d(Constants.TAG, "bits: " + bits.toString());
+
+ final int CHUNK_SIZE = 13;
+ final int LAST_CHUNK_INDEX = fingerprintBlobTruncated.length * 8 / CHUNK_SIZE; // 12
+ Log.d(Constants.TAG, "LAST_CHUNK_INDEX: " + LAST_CHUNK_INDEX);
+
+ int from = 0;
+ int to = CHUNK_SIZE;
+ for (int i = 0; i < (LAST_CHUNK_INDEX + 1); i++) {
+ Log.d(Constants.TAG, "from: " + from + " to: " + to);
+
+ BitSet setIndex = bits.get(from, to);
+ int wordIndex = (int) bitSetToLong(setIndex);
+ // int wordIndex = (int) setIndex.toLongArray()[0]; // min API 19
+
+ fingerprint += words.get(wordIndex);
+
+ if (i != LAST_CHUNK_INDEX) {
+ // line break every 3 words
+ if (to % (CHUNK_SIZE * 3) == 0) {
+ fingerprint += "\n";
+ } else {
+ fingerprint += " ";
+ }
+ }
+
+ from = to;
+ to += CHUNK_SIZE;
+ }
+
+ return fingerprint;
+ }
+
+ /**
+ * Returns a BitSet containing the values in bytes.
+ * BIG ENDIAN!
+ */
+ private static BitSet bitSetToByteArray(byte[] bytes) {
+ int arrayLength = bytes.length * 8;
+ BitSet bits = new BitSet();
+
+ for (int i = 0; i < arrayLength; i++) {
+ if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
+ bits.set(i);
+ }
+ }
+ return bits;
+ }
+
+ private static long bitSetToLong(BitSet bits) {
+ long value = 0L;
+ for (int i = 0; i < bits.length(); ++i) {
+ value += bits.get(i) ? (1L << i) : 0L;
+ }
+ return value;
+ }
+}
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 3d98034d2..284c17e7a 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
@@ -24,9 +24,12 @@ import android.graphics.PorterDuff;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
+import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import org.openintents.openpgp.OpenPgpDecryptionResult;
+import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.asn1.ASN1ObjectIdentifier;
import org.spongycastle.asn1.nist.NISTNamedCurves;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
@@ -34,6 +37,8 @@ import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+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.util.Log;
@@ -218,14 +223,11 @@ public class KeyFormattingUtils {
public static String convertFingerprintToHex(byte[] fingerprint) {
// NOTE: Even though v3 keys are not imported we need to support both fingerprints for
// display/comparison before import
- // Also better cut of unneeded parts, e.g., for fingerprints returned from YubiKeys
- if (fingerprint.length < 20) {
- // v3 key fingerprint with 128 bit (MD5)
- return Hex.toHexString(fingerprint, 0, 16).toLowerCase(Locale.ENGLISH);
- } else {
- // v4 key fingerprint with 160 bit (SHA1)
- return Hex.toHexString(fingerprint, 0, 20).toLowerCase(Locale.ENGLISH);
+ if (fingerprint.length != 16 && fingerprint.length != 20) {
+ throw new IllegalArgumentException("No valid v3 or v4 fingerprint!");
}
+
+ return Hex.toHexString(fingerprint).toLowerCase(Locale.ENGLISH);
}
public static long getKeyIdFromFingerprint(byte[] fingerprint) {
@@ -383,7 +385,6 @@ public class KeyFormattingUtils {
/**
* Converts the given bytes to a unique RGB color using SHA1 algorithm
*
- * @param bytes
* @return an integer array containing 3 numeric color representations (Red, Green, Black)
* @throws java.security.NoSuchAlgorithmException
* @throws java.security.DigestException
@@ -401,7 +402,7 @@ public class KeyFormattingUtils {
public static final int DEFAULT_COLOR = -1;
- public static enum State {
+ public enum State {
REVOKED,
EXPIRED,
VERIFIED,
@@ -411,7 +412,8 @@ public class KeyFormattingUtils {
UNVERIFIED,
UNKNOWN_KEY,
INVALID,
- NOT_SIGNED
+ NOT_SIGNED,
+ INSECURE
}
public static void setStatusImage(Context context, ImageView statusIcon, State state) {
@@ -427,9 +429,196 @@ public class KeyFormattingUtils {
setStatusImage(context, statusIcon, statusText, state, color, false);
}
+ public interface StatusHolder {
+ ImageView getEncryptionStatusIcon();
+ TextView getEncryptionStatusText();
+
+ ImageView getSignatureStatusIcon();
+ TextView getSignatureStatusText();
+
+ View getSignatureLayout();
+ TextView getSignatureUserName();
+ TextView getSignatureUserEmail();
+ TextView getSignatureAction();
+
+ boolean hasEncrypt();
+
+ }
+
+ @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated
+ public static void setStatus(Context context, StatusHolder holder, DecryptVerifyResult result) {
+
+ if (holder.hasEncrypt()) {
+ OpenPgpDecryptionResult decryptionResult = result.getDecryptionResult();
+
+ int encText, encIcon, encColor;
+
+ switch (decryptionResult.getResult()) {
+ case OpenPgpDecryptionResult.RESULT_ENCRYPTED: {
+ encText = R.string.decrypt_result_encrypted;
+ encIcon = R.drawable.status_lock_closed_24dp;
+ encColor = R.color.key_flag_green;
+ break;
+ }
+
+ case OpenPgpDecryptionResult.RESULT_INSECURE: {
+ encText = R.string.decrypt_result_insecure;
+ encIcon = R.drawable.status_signature_invalid_cutout_24dp;
+ encColor = R.color.key_flag_red;
+ break;
+ }
+
+ default:
+ case OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED: {
+ encText = R.string.decrypt_result_not_encrypted;
+ encIcon = R.drawable.status_lock_open_24dp;
+ encColor = R.color.key_flag_red;
+ break;
+ }
+ }
+
+ int encColorRes = context.getResources().getColor(encColor);
+ holder.getEncryptionStatusIcon().setColorFilter(encColorRes, PorterDuff.Mode.SRC_IN);
+ holder.getEncryptionStatusIcon().setImageDrawable(context.getResources().getDrawable(encIcon));
+ holder.getEncryptionStatusText().setText(encText);
+ holder.getEncryptionStatusText().setTextColor(encColorRes);
+ }
+
+ OpenPgpSignatureResult signatureResult = result.getSignatureResult();
+
+ int sigText, sigIcon, sigColor;
+ int sigActionText, sigActionIcon;
+
+ switch (signatureResult.getResult()) {
+
+ case OpenPgpSignatureResult.RESULT_NO_SIGNATURE: {
+ // no signature
+
+ sigText = R.string.decrypt_result_no_signature;
+ sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
+ sigColor = R.color.key_flag_gray;
+
+ // won't be used, but makes compiler happy
+ sigActionText = 0;
+ sigActionIcon = 0;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_VALID_CONFIRMED: {
+ sigText = R.string.decrypt_result_signature_certified;
+ sigIcon = R.drawable.status_signature_verified_cutout_24dp;
+ sigColor = R.color.key_flag_green;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_VALID_UNCONFIRMED: {
+ sigText = R.string.decrypt_result_signature_uncertified;
+ sigIcon = R.drawable.status_signature_unverified_cutout_24dp;
+ sigColor = R.color.key_flag_orange;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_INVALID_KEY_REVOKED: {
+ sigText = R.string.decrypt_result_signature_revoked_key;
+ sigIcon = R.drawable.status_signature_revoked_cutout_24dp;
+ sigColor = R.color.key_flag_red;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED: {
+ sigText = R.string.decrypt_result_signature_expired_key;
+ sigIcon = R.drawable.status_signature_expired_cutout_24dp;
+ sigColor = R.color.key_flag_red;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_KEY_MISSING: {
+ sigText = R.string.decrypt_result_signature_missing_key;
+ sigIcon = R.drawable.status_signature_unknown_cutout_24dp;
+ sigColor = R.color.key_flag_red;
+
+ sigActionText = R.string.decrypt_result_action_Lookup;
+ sigActionIcon = R.drawable.ic_file_download_grey_24dp;
+ break;
+ }
+
+ case OpenPgpSignatureResult.RESULT_INVALID_INSECURE: {
+ sigText = R.string.decrypt_result_insecure_cryptography;
+ sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
+ sigColor = R.color.key_flag_red;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ default:
+ case OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE: {
+ sigText = R.string.decrypt_result_invalid_signature;
+ sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
+ sigColor = R.color.key_flag_red;
+
+ sigActionText = R.string.decrypt_result_action_show;
+ sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ break;
+ }
+
+ }
+
+ int sigColorRes = context.getResources().getColor(sigColor);
+ holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN);
+ holder.getSignatureStatusIcon().setImageDrawable(context.getResources().getDrawable(sigIcon));
+ holder.getSignatureStatusText().setText(sigText);
+ holder.getSignatureStatusText().setTextColor(sigColorRes);
+
+ if (signatureResult.getResult() != OpenPgpSignatureResult.RESULT_NO_SIGNATURE) {
+ // has a signature, thus display layouts
+
+ holder.getSignatureLayout().setVisibility(View.VISIBLE);
+
+ holder.getSignatureAction().setText(sigActionText);
+ holder.getSignatureAction().setCompoundDrawablesWithIntrinsicBounds(
+ 0, 0, sigActionIcon, 0);
+
+ String userId = result.getSignatureResult().getPrimaryUserId();
+ KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
+ if (userIdSplit.name != null) {
+ holder.getSignatureUserName().setText(userIdSplit.name);
+ } else {
+ holder.getSignatureUserName().setText(R.string.user_id_no_name);
+ }
+ if (userIdSplit.email != null) {
+ holder.getSignatureUserEmail().setVisibility(View.VISIBLE);
+ holder.getSignatureUserEmail().setText(userIdSplit.email);
+ } else {
+ holder.getSignatureUserEmail().setVisibility(View.GONE);
+ }
+
+ } else {
+
+ holder.getSignatureLayout().setVisibility(View.GONE);
+
+ }
+
+
+ }
+
/**
* Sets status image based on constant
*/
+ @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21
public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText,
State state, int color, boolean big) {
switch (state) {
@@ -443,7 +632,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);
@@ -456,7 +645,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);
@@ -475,7 +664,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);
@@ -488,7 +677,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);
@@ -507,7 +696,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);
@@ -525,7 +714,25 @@ 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);
+ if (statusText != null) {
+ statusText.setTextColor(context.getResources().getColor(color));
+ }
+ break;
+ }
+ case INSECURE: {
+ if (big) {
+ statusIcon.setImageDrawable(
+ context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_96dp));
+ } else {
+ statusIcon.setImageDrawable(
+ context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp));
+ }
+ if (color == KeyFormattingUtils.DEFAULT_COLOR) {
+ color = R.color.key_flag_red;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -538,7 +745,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);
@@ -551,7 +758,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);
@@ -564,7 +771,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);
@@ -578,7 +785,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/Notify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
index 7e07ed818..7dfd56430 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
@@ -38,33 +38,25 @@ import org.sufficientlysecure.keychain.util.FabContainer;
public class Notify {
public static enum Style {
- OK, WARN, ERROR;
+ OK (R.color.android_green_light), WARN(R.color.android_orange_light), ERROR(R.color.android_red_light);
- public void applyToBar(Snackbar bar) {
+ public final int mLineColor;
- switch (this) {
- case OK:
- // bar.actionColorResource(R.color.android_green_light);
- bar.lineColorResource(R.color.android_green_light);
- break;
- case WARN:
- // bar.textColorResource(R.color.android_orange_light);
- bar.lineColorResource(R.color.android_orange_light);
- break;
- case ERROR:
- // bar.textColorResource(R.color.android_red_light);
- bar.lineColorResource(R.color.android_red_light);
- break;
- }
+ Style(int color) {
+ mLineColor = color;
+ }
+ public void applyToBar(Snackbar bar) {
+ bar.lineColorResource(mLineColor);
}
}
public static final int LENGTH_INDEFINITE = 0;
public static final int LENGTH_LONG = 3500;
+ public static final int LENGTH_SHORT = 1500;
public static Showable create(final Activity activity, String text, int duration, Style style,
- final ActionListener actionListener, int actionResId) {
+ final ActionListener actionListener, Integer actionResId) {
final Snackbar snackbar = Snackbar.with(activity)
.type(SnackbarType.MULTI_LINE)
.text(text);
@@ -77,14 +69,16 @@ public class Notify {
style.applyToBar(snackbar);
+ if (actionResId != null) {
+ snackbar.actionLabel(actionResId);
+ }
if (actionListener != null) {
- snackbar.actionLabel(actionResId)
- .actionListener(new ActionClickListener() {
- @Override
- public void onActionClicked(Snackbar snackbar) {
- actionListener.onAction();
- }
- });
+ snackbar.actionListener(new ActionClickListener() {
+ @Override
+ public void onActionClicked(Snackbar snackbar) {
+ actionListener.onAction();
+ }
+ });
}
if (activity instanceof FabContainer) {
@@ -108,6 +102,13 @@ public class Notify {
}
@Override
+ public void show(Fragment fragment, boolean animate) {
+ snackbar.animation(animate);
+ snackbar.dismissOnActionClicked(animate);
+ show(fragment);
+ }
+
+ @Override
public void show(Fragment fragment) {
if (fragment != null) {
View view = fragment.getView();
@@ -134,7 +135,7 @@ public class Notify {
}
public static Showable create(Activity activity, String text, int duration, Style style) {
- return create(activity, text, duration, style, null, -1);
+ return create(activity, text, duration, style, null, null);
}
public static Showable create(Activity activity, String text, Style style) {
@@ -159,24 +160,26 @@ public class Notify {
/**
* Shows the notification on the bottom of the Activity.
*/
- public void show();
+ void show();
+
+ void show(Fragment fragment, boolean animate);
/**
* Shows the notification on the bottom of the Fragment.
*/
- public void show(Fragment fragment);
+ void show(Fragment fragment);
/**
* Shows the notification on the given ViewGroup.
* The viewGroup should be either a RelativeLayout or FrameLayout.
*/
- public void show(ViewGroup viewGroup);
+ void show(ViewGroup viewGroup);
}
public interface ActionListener {
- public void onAction();
+ void onAction();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java
index 5f71abdab..a6394a3fb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.util;
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.net.Uri;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
@@ -33,17 +34,24 @@ import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Hashtable;
+import java.util.Locale;
/**
* Copied from Bitcoin Wallet
*/
public class QrCodeUtils {
+ public static Bitmap getQRCodeBitmap(final Uri uri, final int size) {
+ // for URIs we want alphanumeric encoding to save space, thus make everything upper case!
+ // zxing will then select Mode.ALPHANUMERIC internally
+ return getQRCodeBitmap(uri.toString().toUpperCase(Locale.ENGLISH), size);
+ }
+
/**
* Generate Bitmap with QR Code based on input.
* @return QR Code as Bitmap
*/
- public static Bitmap getQRCodeBitmap(final String input, final int size) {
+ private static Bitmap getQRCodeBitmap(final String input, final int size) {
try {
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..375483d89
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java
@@ -0,0 +1,83 @@
+/*
+ * 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.app.Activity;
+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 Activity mContext;
+ private Preferences mPreferences;
+ private String mCurrentTheme = null;
+
+ private int mLightResId;
+ private int mDarkResId;
+
+ 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_Keychain_Dark);
+ } else {
+ return new ContextThemeWrapper(context, R.style.Theme_Keychain_Light);
+ }
+ }
+
+ public void setThemes(int lightResId, int darkResId) {
+ mLightResId = lightResId;
+ mDarkResId = darkResId;
+ }
+
+ public ThemeChanger(Activity 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 = mLightResId;
+ if (Constants.Pref.Theme.DARK.equals(newTheme)) {
+ themeId = mDarkResId;
+ }
+
+ 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 e5b3df8c9..6a51085f3 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
@@ -25,14 +25,12 @@ import android.support.annotation.StringRes;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.AttributeSet;
-import android.widget.ImageView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
-import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
-import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
+import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
public class CertifyKeySpinner extends KeySpinner {
private long mHiddenMasterKeyId = Constants.key.none;
@@ -61,19 +59,9 @@ public class CertifyKeySpinner extends KeySpinner {
// sample only has one Loader, so we don't care about the ID.
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
- // These are the rows that we will retrieve.
- String[] projection = new String[]{
- KeychainContract.KeyRings._ID,
- KeychainContract.KeyRings.MASTER_KEY_ID,
- KeychainContract.KeyRings.KEY_ID,
- KeychainContract.KeyRings.USER_ID,
- KeychainContract.KeyRings.IS_REVOKED,
- KeychainContract.KeyRings.IS_EXPIRED,
+ String[] projection = KeyAdapter.getProjectionWith(new String[] {
KeychainContract.KeyRings.HAS_CERTIFY,
- KeychainContract.KeyRings.HAS_ANY_SECRET,
- KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID,
- KeychainContract.KeyRings.CREATION
- };
+ });
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND "
+ KeychainDatabase.Tables.KEYS + "." + KeychainContract.KeyRings.MASTER_KEY_ID
@@ -84,7 +72,7 @@ public class CertifyKeySpinner extends KeySpinner {
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
}
- private int mIndexHasCertify, mIndexIsRevoked, mIndexIsExpired;
+ private int mIndexHasCertify;
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
@@ -92,16 +80,13 @@ public class CertifyKeySpinner extends KeySpinner {
if (loader.getId() == LOADER_ID) {
mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY);
- mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED);
- mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED);
// If:
// - no key has been pre-selected (e.g. by SageSlinger)
// - there are actually keys (not just "none" entry)
// Then:
// - select key that is capable of certifying, but only if there is only one key capable of it
- mIsSingle = false;
- if (mSelectedKeyId == Constants.key.none && mAdapter.getCount() > 1) {
+ if (mPreSelectedKeyId == Constants.key.none && mAdapter.getCount() > 1) {
// preselect if key can certify
int selection = -1;
while (data.moveToNext()) {
@@ -127,18 +112,19 @@ public class CertifyKeySpinner extends KeySpinner {
}
@Override
- boolean setStatus(Context context, Cursor cursor, ImageView statusView) {
- if (cursor.getInt(mIndexIsRevoked) != 0) {
- KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray);
+ 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(mIndexIsExpired) != 0) {
- KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray);
+ 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)) {
- KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray);
+ if (cursor.isNull(mIndexHasCertify)) {
return false;
}
@@ -146,6 +132,7 @@ public class CertifyKeySpinner extends KeySpinner {
return true;
}
+ @Override
public @StringRes int getNoneString() {
return R.string.choice_select_cert;
}
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/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java
index 63a1aade9..48e6c2cee 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java
@@ -38,6 +38,7 @@ import com.tokenautocomplete.TokenCompleteTextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
@@ -126,7 +127,13 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// These are the rows that we will retrieve.
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
- String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND " + KeyRings.IS_EXPIRED + " = 0 AND "
+
+ String[] projection = KeyAdapter.getProjectionWith(new String[] {
+ KeychainContract.KeyRings.HAS_ENCRYPT,
+ });
+
+ String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND "
+ + KeyRings.IS_EXPIRED + " = 0 AND "
+ Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0";
if (args != null && args.containsKey(ARG_QUERY)) {
@@ -135,12 +142,12 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
where += " AND " + KeyRings.USER_ID + " LIKE ?";
- return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where,
+ return new CursorLoader(getContext(), baseUri, projection, where,
new String[]{"%" + query + "%"}, null);
}
mAdapter.setSearchQuery(null);
- return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, null, null);
+ return new CursorLoader(getContext(), baseUri, projection, where, null, null);
}
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 61b7c718b..e3ec3d34b 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
@@ -17,41 +17,48 @@
package org.sufficientlysecure.keychain.ui.widget;
+
import android.content.Context;
import android.database.Cursor;
-import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
-import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.AppCompatSpinner;
-import android.text.format.DateUtils;
import android.util.AttributeSet;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
-import android.widget.ImageView;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
-import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
+import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
+
/**
* Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
* Related: http://stackoverflow.com/a/27713090
*/
-public abstract class KeySpinner extends AppCompatSpinner implements LoaderManager.LoaderCallbacks<Cursor> {
+public abstract class KeySpinner extends AppCompatSpinner implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+
+ public static final String ARG_SUPER_STATE = "super_state";
+ public static final String ARG_KEY_ID = "key_id";
+
public interface OnKeyChangedListener {
- public void onKeyChanged(long masterKeyId);
+ void onKeyChanged(long masterKeyId);
}
- protected long mSelectedKeyId = Constants.key.none;
+ protected long mPreSelectedKeyId = Constants.key.none;
protected SelectKeyAdapter mAdapter = new SelectKeyAdapter();
protected OnKeyChangedListener mListener;
@@ -79,7 +86,8 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag
@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);
}
}
@@ -111,7 +119,8 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag
if (getContext() instanceof FragmentActivity) {
((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
} else {
- Log.e(Constants.TAG, "KeySpinner must be attached to FragmentActivity, this is " + getContext().getClass());
+ throw new AssertionError("KeySpinner must be attached to FragmentActivity, this is "
+ + getContext().getClass());
}
}
@@ -129,172 +138,134 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag
}
}
- public void setSelectedKeyId(long selectedKeyId) {
- this.mSelectedKeyId = selectedKeyId;
+ public long getSelectedKeyId() {
+ Object item = getSelectedItem();
+ return getSelectedKeyId(item);
+ }
+
+ public long getSelectedKeyId(Object item) {
+ if (item instanceof KeyItem) {
+ return ((KeyItem) item).mKeyId;
+ }
+ return Constants.key.none;
+ }
+
+ public void setPreSelectedKeyId(long selectedKeyId) {
+ mPreSelectedKeyId = selectedKeyId;
}
protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter {
- private CursorAdapter inner;
- private int mIndexUserId;
- private int mIndexDuplicate;
+ private KeyAdapter inner;
private int mIndexMasterKeyId;
- private int mIndexCreationDate;
public SelectKeyAdapter() {
- inner = new CursorAdapter(getContext(), null, 0) {
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return View.inflate(getContext(), R.layout.keyspinner_item, null);
- }
+ inner = new KeyAdapter(getContext(), null, 0) {
@Override
- public void bindView(View view, Context context, Cursor cursor) {
- TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);
- ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
- TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
- TextView vDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
-
- KeyRing.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexUserId));
- vKeyName.setText(userId.name);
- vKeyEmail.setText(userId.email);
-
- boolean duplicate = cursor.getLong(mIndexDuplicate) > 0;
- if (duplicate) {
- String dateTime = DateUtils.formatDateTime(context,
- cursor.getLong(mIndexCreationDate) * 1000,
- DateUtils.FORMAT_SHOW_DATE
- | DateUtils.FORMAT_SHOW_TIME
- | DateUtils.FORMAT_SHOW_YEAR
- | DateUtils.FORMAT_ABBREV_MONTH);
-
- vDuplicate.setText(context.getString(R.string.label_key_created, dateTime));
- vDuplicate.setVisibility(View.VISIBLE);
- } else {
- vDuplicate.setVisibility(View.GONE);
- }
-
- boolean valid = setStatus(getContext(), cursor, vKeyStatus);
- setItemEnabled(view, valid);
+ public boolean isEnabled(Cursor cursor) {
+ return KeySpinner.this.isItemEnabled(cursor);
}
- @Override
- public long getItemId(int position) {
- try {
- return ((Cursor) getItem(position)).getLong(mIndexMasterKeyId);
- } catch (Exception e) {
- // This can happen on concurrent modification :(
- return Constants.key.none;
- }
- }
};
}
- private void setItemEnabled(View view, boolean enabled) {
- TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);
- ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
- TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
- TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
-
- if (enabled) {
- vKeyName.setTextColor(Color.BLACK);
- vKeyEmail.setTextColor(Color.BLACK);
- vKeyDuplicate.setTextColor(Color.BLACK);
- vKeyStatus.setVisibility(View.GONE);
- view.setClickable(false);
- } else {
- vKeyName.setTextColor(Color.GRAY);
- vKeyEmail.setTextColor(Color.GRAY);
- vKeyDuplicate.setTextColor(Color.GRAY);
- vKeyStatus.setVisibility(View.VISIBLE);
- // this is a HACK. the trick is, if the element itself is clickable, the
- // click is not passed on to the view list
- view.setClickable(true);
- }
- }
-
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == null) return inner.swapCursor(null);
- mIndexDuplicate = newCursor.getColumnIndex(KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID);
- mIndexUserId = newCursor.getColumnIndex(KeychainContract.KeyRings.USER_ID);
mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID);
- mIndexCreationDate = newCursor.getColumnIndex(KeychainContract.KeyRings.CREATION);
- // pre-select key if mSelectedKeyId is given
- if (mSelectedKeyId != Constants.key.none && newCursor.moveToFirst()) {
+ Cursor oldCursor = inner.swapCursor(newCursor);
+
+ // pre-select key if mPreSelectedKeyId is given
+ if (mPreSelectedKeyId != Constants.key.none && newCursor.moveToFirst()) {
do {
- if (newCursor.getLong(mIndexMasterKeyId) == mSelectedKeyId) {
- setSelection(newCursor.getPosition() + 1);
+ if (newCursor.getLong(mIndexMasterKeyId) == mPreSelectedKeyId) {
+ setSelection(newCursor.getPosition() +1);
}
} while (newCursor.moveToNext());
}
- return inner.swapCursor(newCursor);
+ return oldCursor;
}
@Override
public int getCount() {
- return inner.getCount() + 1;
+ return inner.getCount() +1;
}
@Override
- public Object getItem(int position) {
- if (position == 0) return null;
- return inner.getItem(position - 1);
+ public KeyItem getItem(int position) {
+ if (position == 0) {
+ return null;
+ }
+ return inner.getItem(position -1);
}
@Override
public long getItemId(int position) {
- if (position == 0) return Constants.key.none;
- return inner.getItemId(position - 1);
+ if (position == 0) {
+ return Constants.key.none;
+ }
+ return inner.getItemId(position -1);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- try {
- View v = getDropDownView(position, convertView, parent);
- v.findViewById(R.id.keyspinner_key_email).setVisibility(View.GONE);
- return v;
- } catch (NullPointerException e) {
- // This is for the preview...
- return View.inflate(getContext(), android.R.layout.simple_list_item_1, null);
- }
- }
- @Override
- public View getDropDownView(int position, View convertView, ViewGroup parent) {
- View view;
- if (position == 0) {
- if (convertView == null) {
- view = inner.newView(null, null, parent);
- } else {
- view = convertView;
+ // Unfortunately, SpinnerAdapter does not support multiple view
+ // types. For this reason, we throw away convertViews of a bad
+ // type. This is sort of a hack, but since the number of elements
+ // we deal with in KeySpinners is usually very small (number of
+ // secret keys), this is the easiest solution. (I'm sorry.)
+ if (convertView != null) {
+ // This assumes that the inner view has non-null tags on its views!
+ boolean isWrongType = (convertView.getTag() == null) != (position == 0);
+ if (isWrongType) {
+ convertView = null;
}
- TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);
- ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
- TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
- TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
-
- vKeyName.setText(getNoneString());
- vKeyEmail.setVisibility(View.GONE);
- vKeyDuplicate.setVisibility(View.GONE);
- vKeyStatus.setVisibility(View.GONE);
- setItemEnabled(view, true);
- } else {
- view = inner.getView(position - 1, convertView, parent);
- TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
- vKeyEmail.setVisibility(View.VISIBLE);
}
+
+ if (position > 0) {
+ return inner.getView(position -1, convertView, parent);
+ }
+
+ View view = convertView != null ? convertView :
+ LayoutInflater.from(getContext()).inflate(
+ R.layout.keyspinner_item_none, parent, false);
+ ((TextView) view.findViewById(R.id.keyspinner_key_name)).setText(getNoneString());
return view;
}
}
- boolean setStatus(Context context, Cursor cursor, ImageView statusView) {
+ boolean isItemEnabled(Cursor cursor) {
return true;
}
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ Bundle bundle = (Bundle) state;
+
+ mPreSelectedKeyId = bundle.getLong(ARG_KEY_ID);
+
+ // restore super state
+ super.onRestoreInstanceState(bundle.getParcelable(ARG_SUPER_STATE));
+
+ }
+
+ @NonNull
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Bundle bundle = new Bundle();
+
+ // save super state
+ bundle.putParcelable(ARG_SUPER_STATE, super.onSaveInstanceState());
+
+ bundle.putLong(ARG_KEY_ID, getSelectedKeyId());
+ return bundle;
+ }
+
public @StringRes int getNoneString() {
- return R.string.choice_none;
+ return R.string.cert_none;
}
}
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 1487c3053..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,26 +99,23 @@ public class PasswordStrengthView extends View {
public PasswordStrengthView(Context context, AttributeSet attrs) {
super(context, attrs);
- int COLOR_FAIL = context.getResources().getColor(R.color.android_red_light);
- int COLOR_WEAK = context.getResources().getColor(R.color.android_orange_light);
- int COLOR_STRONG = context.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,
R.styleable.PasswordStrengthView,
0, 0);
- try {
- mStrengthRequirement = style.getInteger(R.styleable.PasswordStrengthView_strength,
- STRENGTH_MEDIUM);
- mShowGuides = style.getBoolean(R.styleable.PasswordStrengthView_showGuides, true);
- mColorFail = style.getColor(R.styleable.PasswordStrengthView_color_fail, COLOR_FAIL);
- mColorWeak = style.getColor(R.styleable.PasswordStrengthView_color_weak, COLOR_WEAK);
- mColorStrong = style.getColor(R.styleable.PasswordStrengthView_color_strong,
- COLOR_STRONG);
- } catch (Exception e) {
- e.printStackTrace();
- }
+ mStrengthRequirement = style.getInteger(R.styleable.PasswordStrengthView_strength,
+ STRENGTH_MEDIUM);
+ mShowGuides = style.getBoolean(R.styleable.PasswordStrengthView_showGuides, true);
+ mColorFail = style.getColor(R.styleable.PasswordStrengthView_color_fail, COLOR_FAIL);
+ mColorWeak = style.getColor(R.styleable.PasswordStrengthView_color_weak, COLOR_WEAK);
+ mColorStrong = style.getColor(R.styleable.PasswordStrengthView_color_strong,
+ COLOR_STRONG);
+
// Create and style the paint used for drawing the guide on the indicator
mGuidePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mGuidePaint.setStyle(Paint.Style.FILL_AND_STROKE);
@@ -124,6 +123,9 @@ public class PasswordStrengthView extends View {
// Create and style paint for indicator
mIndicatorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mIndicatorPaint.setStyle(Paint.Style.FILL);
+
+ style.recycle();
+
}
/**
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 df7347fa4..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
@@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui.widget;
+
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
@@ -24,12 +25,9 @@ import android.os.Bundle;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.AttributeSet;
-import android.widget.ImageView;
-import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
-import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
-import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
+import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
public class SignKeySpinner extends KeySpinner {
public SignKeySpinner(Context context) {
@@ -50,19 +48,9 @@ public class SignKeySpinner extends KeySpinner {
// sample only has one Loader, so we don't care about the ID.
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
- // These are the rows that we will retrieve.
- String[] projection = new String[]{
- KeychainContract.KeyRings._ID,
- KeychainContract.KeyRings.MASTER_KEY_ID,
- KeychainContract.KeyRings.KEY_ID,
- KeychainContract.KeyRings.USER_ID,
- KeychainContract.KeyRings.IS_REVOKED,
- KeychainContract.KeyRings.IS_EXPIRED,
+ String[] projection = KeyAdapter.getProjectionWith(new String[] {
KeychainContract.KeyRings.HAS_SIGN,
- KeychainContract.KeyRings.HAS_ANY_SECRET,
- KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID,
- KeychainContract.KeyRings.CREATION
- };
+ });
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1";
@@ -71,7 +59,7 @@ public class SignKeySpinner extends KeySpinner {
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
}
- private int mIndexHasSign, mIndexIsRevoked, mIndexIsExpired;
+ private int mIndexHasSign;
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
@@ -79,23 +67,23 @@ public class SignKeySpinner extends KeySpinner {
if (loader.getId() == LOADER_ID) {
mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN);
- mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED);
- mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED);
}
}
@Override
- boolean setStatus(Context context, Cursor cursor, ImageView statusView) {
- if (cursor.getInt(mIndexIsRevoked) != 0) {
- KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray);
+ 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(mIndexIsExpired) != 0) {
- KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray);
+ if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
return false;
}
- if (cursor.getInt(mIndexHasSign) == 0) {
- KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray);
+ if (cursor.isNull(mIndexHasSign)) {
return false;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java
new file mode 100644
index 000000000..18e830139
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
+ *
+ * The MIT License (MIT)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.sufficientlysecure.keychain.ui.widget;
+
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ViewAnimator;
+
+import org.sufficientlysecure.keychain.R;
+
+
+/** This view is essentially identical to ViewAnimator, but allows specifying the initial view
+ * for preview as an xml attribute. */
+public class ToolableViewAnimator extends ViewAnimator {
+
+ private int mInitChild = -1;
+
+ public ToolableViewAnimator(Context context) {
+ super(context);
+ }
+
+ public ToolableViewAnimator(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ if (isInEditMode()) {
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ToolableViewAnimator);
+ mInitChild = a.getInt(R.styleable.ToolableViewAnimator_initialView, -1);
+ a.recycle();
+ }
+ }
+
+ public ToolableViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs);
+
+ if (isInEditMode()) {
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ToolableViewAnimator, defStyleAttr, 0);
+ mInitChild = a.getInt(R.styleable.ToolableViewAnimator_initialView, -1);
+ a.recycle();
+ }
+ }
+
+ @Override
+ public void addView(@NonNull View child, int index, ViewGroup.LayoutParams params) {
+ if (isInEditMode() && mInitChild-- > 0) {
+ return;
+ }
+ super.addView(child, index, params);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java
index e1efd5abc..77aa1a055 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java
@@ -303,10 +303,13 @@ public class ContactHelper {
Cursor contactMasterKey = context.getContentResolver().query(contactUri,
new String[]{ContactsContract.Data.DATA2}, null, null, null);
if (contactMasterKey != null) {
- if (contactMasterKey.moveToNext()) {
- return KeychainContract.KeyRings.buildGenericKeyRingUri(contactMasterKey.getLong(0));
+ try {
+ if (contactMasterKey.moveToNext()) {
+ return KeychainContract.KeyRings.buildGenericKeyRingUri(contactMasterKey.getLong(0));
+ }
+ } finally {
+ contactMasterKey.close();
}
- contactMasterKey.close();
}
return null;
}
@@ -537,7 +540,7 @@ public class ContactHelper {
KEYS_TO_CONTACT_PROJECTION,
KeychainContract.KeyRings.HAS_ANY_SECRET + "!=0",
null, null);
- if (cursor != null) {
+ if (cursor != null) try {
while (cursor.moveToNext()) {
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0;
@@ -565,6 +568,8 @@ public class ContactHelper {
}
}
}
+ } finally {
+ cursor.close();
}
for (long masterKeyId : keysToDelete) {
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 8334b37ec..d7491ab26 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
@@ -18,16 +18,16 @@
package org.sufficientlysecure.keychain.util;
import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Messenger;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.Keyserver;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
+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;
@@ -35,27 +35,40 @@ 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> {
- public static void importContacts(Context context, Messenger messenger) {
- importAll(context, messenger, ContactHelper.getContactMails(context));
- }
+ private ArrayList<ParcelableKeyRing> mKeyList;
+ private String mKeyserver;
- public static void importAll(Context context, Messenger messenger, List<String> mails) {
- // Collect all candidates as ImportKeysListEntry (set for deduplication)
- Set<ImportKeysListEntry> entries = new HashSet<>();
- for (String mail : mails) {
- entries.addAll(getEmailKeys(context, mail));
+ public ImportContactKeysCallback(Context context, String keyserver, Proxy proxy) {
+ this(context, ContactHelper.getContactMails(context), keyserver, proxy);
}
- // Put them in a list and import
- ArrayList<ParcelableKeyRing> keys = new ArrayList<>(entries.size());
- for (ImportKeysListEntry entry : entries) {
- keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(), null));
+ 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, proxy));
+ }
+
+ // Put them in a list and import
+ ArrayList<ParcelableKeyRing> keys = new ArrayList<>(entries.size());
+ for (ImportKeysListEntry entry : entries) {
+ keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(), null));
+ }
+ mKeyList = keys;
+ mKeyserver = keyserver;
+ }
+ @Override
+ public ImportKeyringParcel createOperationInput() {
+ return new ImportKeyringParcel(mKeyList, mKeyserver);
}
- importKeys(context, messenger, keys);
}
- 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
@@ -63,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));
}
}
@@ -72,27 +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;
}
- private static void importKeys(Context context, Messenger messenger, ArrayList<ParcelableKeyRing> keys) {
- Intent importIntent = new Intent(context, KeychainIntentService.class);
- importIntent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
- Bundle importData = new Bundle();
- importData.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keys);
- importIntent.putExtra(KeychainIntentService.EXTRA_DATA, importData);
- importIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- context.startService(importIntent);
- }
-
- 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 7efb7c5af..45dc33906 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
@@ -17,41 +17,40 @@
package org.sufficientlysecure.keychain.util;
-import android.app.ProgressDialog;
+
+import java.io.File;
+
import android.content.Intent;
-import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
+import android.net.Uri;
import android.support.v4.app.FragmentActivity;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
-import java.io.File;
-
-public class ExportHelper {
+public class ExportHelper
+ implements CryptoOperationHelper.Callback <ExportKeyringParcel, ExportResult> {
protected File mExportFile;
FragmentActivity mActivity;
+ private boolean mExportSecret;
+ private long[] mMasterKeyIds;
+
public ExportHelper(FragmentActivity activity) {
super();
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 {
@@ -59,72 +58,67 @@ 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() {
+ FileHelper.saveDocumentDialog(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
+ // TODO: this class should be able to call mExportOpHelper.handleActivity
+
/**
* Export keys
*/
public void exportKeys(long[] masterKeyIds, boolean exportSecret) {
Log.d(Constants.TAG, "exportKeys started");
+ mExportSecret = exportSecret;
+ mMasterKeyIds = masterKeyIds; // if masterKeyIds is null it means export all
- // Send all information needed to service to export key in other thread
- final Intent intent = new Intent(mActivity, KeychainIntentService.class);
-
- intent.setAction(KeychainIntentService.ACTION_EXPORT_KEYRING);
-
- // fill values for this action
- Bundle data = new Bundle();
-
- data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFile.getAbsolutePath());
- data.putBoolean(KeychainIntentService.EXPORT_SECRET, exportSecret);
-
- if (masterKeyIds == null) {
- data.putBoolean(KeychainIntentService.EXPORT_ALL, true);
- } else {
- data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, masterKeyIds);
- }
-
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Message is received after exporting is done in KeychainIntentService
- ServiceProgressHandler exportHandler = new ServiceProgressHandler(mActivity,
- mActivity.getString(R.string.progress_exporting),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
+ CryptoOperationHelper<ExportKeyringParcel, ExportResult> exportOpHelper =
+ new CryptoOperationHelper<>(1, mActivity, this, R.string.progress_exporting);
+ exportOpHelper.cryptoOperation();
+ }
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle data = message.getData();
+ @Override
+ public ExportKeyringParcel createOperationInput() {
+ return new ExportKeyringParcel(mMasterKeyIds, mExportSecret, mExportFile.getAbsolutePath());
+ }
- ExportResult result = data.getParcelable(ExportResult.EXTRA_RESULT);
- result.createNotify(mActivity).show();
- }
- }
- };
+ @Override
+ final public void onCryptoOperationSuccess(ExportResult result) {
+ // trigger scan of the created 'media' file so it shows up on MTP
+ // http://stackoverflow.com/questions/13737261/nexus-4-not-showing-files-via-mtp
+ mActivity.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(mExportFile)));
+ result.createNotify(mActivity).show();
+ }
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(exportHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+ @Override
+ public void onCryptoOperationCancelled() {
- // show progress dialog
- exportHandler.showProgressDialog(mActivity);
+ }
- // start service with intent
- mActivity.startService(intent);
+ @Override
+ public void onCryptoOperationError(ExportResult result) {
+ result.createNotify(mActivity).show();
}
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java
index 677acb1b8..9fb362412 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.util;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ActivityNotFoundException;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
@@ -27,55 +28,116 @@ import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.provider.DocumentsContract;
import android.provider.OpenableColumns;
+import android.support.annotation.StringRes;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.widget.Toast;
+import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
+
+/** This class offers a number of helper functions for saving documents.
+ *
+ * There are three entry points here: openDocument, saveDocument and
+ * saveDocumentDialog. Each behaves a little differently depending on whether
+ * the Android version used is pre or post KitKat.
+ *
+ * - openDocument queries for a document for reading. Used in "open encrypted
+ * file" ui flow. On pre-kitkat, this relies on an external file manager,
+ * and will fail with a toast message if none is installed.
+ *
+ * - saveDocument queries for a document name for saving. on pre-kitkat, this
+ * shows a dialog where a filename can be input. on kitkat and up, it
+ * directly triggers a "save document" intent. Used in "save encrypted file"
+ * ui flow.
+ *
+ * - saveDocumentDialog queries for a document. this shows a dialog on all
+ * versions of android. the browse button opens an external browser on
+ * pre-kitkat or the "save document" intent on post-kitkat devices. Used in
+ * "backup key" ui flow.
+ *
+ * It is noteworthy that the "saveDocument" call is essentially substituted
+ * by the "saveDocumentDialog" on pre-kitkat devices.
+ *
+ */
public class FileHelper {
- /**
- * Checks if external storage is mounted if file is located on external storage
- *
- * @param file
- * @return true if storage is mounted
- */
- public static boolean isStorageMounted(String file) {
- if (file.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
- if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- return false;
- }
+ public static void openDocument(Fragment fragment, Uri last, String mimeType, boolean multiple, int requestCode) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ openDocumentPreKitKat(fragment, last, mimeType, multiple, requestCode);
+ } else {
+ openDocumentKitKat(fragment, mimeType, multiple, requestCode);
}
+ }
- return true;
+ public static void saveDocument(Fragment fragment, String targetName, Uri inputUri,
+ @StringRes int title, @StringRes int message, int requestCode) {
+ saveDocument(fragment, targetName, inputUri, "*/*", title, message, requestCode);
}
- /**
- * Opens the preferred installed file manager on Android and shows a toast if no manager is
- * installed.
- *
- * @param fragment
- * @param last default selected Uri, not supported by all file managers
- * @param mimeType can be text/plain for example
- * @param requestCode requestCode used to identify the result coming back from file manager to
- * onActivityResult() in your activity
- */
- public static void openFile(Fragment fragment, Uri last, String mimeType, int requestCode) {
+ public static void saveDocument(Fragment fragment, String targetName, Uri inputUri, String mimeType,
+ @StringRes int title, @StringRes int message, int requestCode) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ saveDocumentDialog(fragment, targetName, inputUri, title, message, requestCode);
+ } else {
+ saveDocumentKitKat(fragment, mimeType, targetName, requestCode);
+ }
+ }
+
+ public static void saveDocumentDialog(final Fragment fragment, String targetName, Uri inputUri,
+ @StringRes int title, @StringRes int message, final int requestCode) {
+
+ saveDocumentDialog(fragment, targetName, inputUri, title, message, new FileDialogCallback() {
+ // is this a good idea? seems hacky...
+ @Override
+ public void onFileSelected(File file, boolean checked) {
+ Intent intent = new Intent();
+ intent.setData(Uri.fromFile(file));
+ fragment.onActivityResult(requestCode, Activity.RESULT_OK, intent);
+ }
+ });
+ }
+
+ public static void saveDocumentDialog(final Fragment fragment, String targetName, Uri inputUri,
+ @StringRes int title, @StringRes int message, FileDialogCallback callback) {
+
+ File file = inputUri == null ? null : new File(inputUri.getPath());
+ File parentDir = file != null && file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
+ File targetFile = new File(parentDir, targetName);
+ saveDocumentDialog(callback, fragment.getActivity().getSupportFragmentManager(),
+ fragment.getString(title), fragment.getString(message), targetFile, null);
+
+ }
+
+ /** Opens the preferred installed file manager on Android and shows a toast
+ * if no manager is installed. */
+ private static void openDocumentPreKitKat(
+ Fragment fragment, Uri last, String mimeType, boolean multiple, int requestCode) {
+
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
-
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
+ }
intent.setData(last);
intent.setType(mimeType);
@@ -86,11 +148,34 @@ public class FileHelper {
Toast.makeText(fragment.getActivity(), R.string.no_filemanager_installed,
Toast.LENGTH_SHORT).show();
}
+
}
- public static void saveFile(final FileDialogCallback callback, final FragmentManager fragmentManager,
- final String title, final String message, final File defaultFile,
- final String checkMsg) {
+ /** Opens the storage browser on Android 4.4 or later for opening a file */
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private static void openDocumentKitKat(Fragment fragment, String mimeType, boolean multiple, int requestCode) {
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType(mimeType);
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
+ fragment.startActivityForResult(intent, requestCode);
+ }
+
+ /** Opens the storage browser on Android 4.4 or later for saving a file. */
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ public static void saveDocumentKitKat(Fragment fragment, String mimeType, String suggestedName, int requestCode) {
+ Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType(mimeType);
+ intent.putExtra("android.content.extra.SHOW_ADVANCED", true); // Note: This is not documented, but works
+ intent.putExtra(Intent.EXTRA_TITLE, suggestedName);
+ fragment.startActivityForResult(intent, requestCode);
+ }
+
+ public static void saveDocumentDialog(
+ final FileDialogCallback callback, final FragmentManager fragmentManager,
+ final String title, final String message, final File defaultFile,
+ final String checkMsg) {
// Message is received after file is selected
Handler returnHandler = new Handler() {
@Override
@@ -117,63 +202,6 @@ public class FileHelper {
});
}
- public static void saveFile(Fragment fragment, String title, String message, File defaultFile, int requestCode) {
- saveFile(fragment, title, message, defaultFile, requestCode, null);
- }
-
- public static void saveFile(final Fragment fragment, String title, String message, File defaultFile,
- final int requestCode, String checkMsg) {
- saveFile(new FileDialogCallback() {
- @Override
- public void onFileSelected(File file, boolean checked) {
- Intent intent = new Intent();
- intent.setData(Uri.fromFile(file));
- fragment.onActivityResult(requestCode, Activity.RESULT_OK, intent);
- }
- }, fragment.getActivity().getSupportFragmentManager(), title, message, defaultFile, checkMsg);
- }
-
- @TargetApi(Build.VERSION_CODES.KITKAT)
- public static void openDocument(Fragment fragment, String mimeType, int requestCode) {
- openDocument(fragment, mimeType, false, requestCode);
- }
-
- /**
- * Opens the storage browser on Android 4.4 or later for opening a file
- *
- * @param fragment
- * @param mimeType can be text/plain for example
- * @param multiple allow file chooser to return multiple files
- * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your
- */
- @TargetApi(Build.VERSION_CODES.KITKAT)
- public static void openDocument(Fragment fragment, String mimeType, boolean multiple, int requestCode) {
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType(mimeType);
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
-
- fragment.startActivityForResult(intent, requestCode);
- }
-
- /**
- * Opens the storage browser on Android 4.4 or later for saving a file
- *
- * @param fragment
- * @param mimeType can be text/plain for example
- * @param suggestedName a filename desirable for the file to be saved
- * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your
- */
- @TargetApi(Build.VERSION_CODES.KITKAT)
- public static void saveDocument(Fragment fragment, String mimeType, String suggestedName, int requestCode) {
- Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType(mimeType);
- intent.putExtra("android.content.extra.SHOW_ADVANCED", true); // Note: This is not documented, but works
- intent.putExtra(Intent.EXTRA_TITLE, suggestedName);
- fragment.startActivityForResult(intent, requestCode);
- }
-
public static String getFilename(Context context, Uri uri) {
String filename = null;
try {
@@ -234,7 +262,78 @@ public class FileHelper {
return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
}
- public static interface FileDialogCallback {
- public void onFileSelected(File file, boolean checked);
+ public static String readTextFromUri(Context context, Uri outputUri, String charset)
+ throws IOException {
+
+ byte[] decryptedMessage;
+ {
+ InputStream in = context.getContentResolver().openInputStream(outputUri);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] buf = new byte[256];
+ int read;
+ while ( (read = in.read(buf)) > 0) {
+ out.write(buf, 0, read);
+ }
+ in.close();
+ out.close();
+ decryptedMessage = out.toByteArray();
+ }
+
+ String plaintext;
+ if (charset != null) {
+ try {
+ plaintext = new String(decryptedMessage, charset);
+ } catch (UnsupportedEncodingException e) {
+ // if we can't decode properly, just fall back to utf-8
+ plaintext = new String(decryptedMessage);
+ }
+ } else {
+ plaintext = new String(decryptedMessage);
+ }
+
+ return plaintext;
+
+ }
+
+ public static void copyUriData(Context context, Uri fromUri, Uri toUri) throws IOException {
+ BufferedInputStream bis = null;
+ BufferedOutputStream bos = null;
+
+ try {
+ ContentResolver resolver = context.getContentResolver();
+ bis = new BufferedInputStream(resolver.openInputStream(fromUri));
+ bos = new BufferedOutputStream(resolver.openOutputStream(toUri));
+ byte[] buf = new byte[1024];
+ int len;
+ while ( (len = bis.read(buf)) > 0) {
+ bos.write(buf, 0, len);
+ }
+ } finally {
+ try {
+ if (bis != null) {
+ bis.close();
+ }
+ if (bos != null) {
+ bos.close();
+ }
+ } catch (IOException e) {
+ // ignore, it's just stream closin'
+ }
+ }
+ }
+
+ /** Checks if external storage is mounted if file is located on external storage. */
+ public static boolean isStorageMounted(String file) {
+ if (file.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public interface FileDialogCallback {
+ void onFileSelected(File file, boolean checked);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java
index 3bbd86d6a..8a614d64d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java
@@ -51,15 +51,15 @@ public class KeyUpdateHelper {
}
// Start the service and update the keys
- Intent importIntent = new Intent(mContext, KeychainIntentService.class);
- importIntent.setAction(KeychainIntentService.ACTION_DOWNLOAD_AND_IMPORT_KEYS);
+ Intent importIntent = new Intent(mContext, KeychainService.class);
+ importIntent.setAction(KeychainService.ACTION_DOWNLOAD_AND_IMPORT_KEYS);
Bundle importData = new Bundle();
- importData.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST,
+ importData.putParcelableArrayList(KeychainService.DOWNLOAD_KEY_LIST,
new ArrayList<ImportKeysListEntry>(keys));
- importIntent.putExtra(KeychainIntentService.EXTRA_SERVICE_INTENT, importData);
+ importIntent.putExtra(KeychainService.EXTRA_SERVICE_INTENT, importData);
- importIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, new Messenger(mHandler));
+ importIntent.putExtra(KeychainService.EXTRA_MESSENGER, new Messenger(mHandler));
mContext.startService(importIntent);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OrientationUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OrientationUtils.java
new file mode 100644
index 000000000..43ed12429
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OrientationUtils.java
@@ -0,0 +1,85 @@
+/*
+ * 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.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.view.Display;
+import android.view.Surface;
+import android.view.WindowManager;
+
+/**
+ * Static methods related to device orientation.
+ */
+public class OrientationUtils {
+
+ /**
+ * Locks the device window in landscape mode.
+ */
+ public static void lockOrientationLandscape(Activity activity) {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ }
+
+ /**
+ * Locks the device window in portrait mode.
+ */
+ public static void lockOrientationPortrait(Activity activity) {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ /**
+ * Locks the device window in actual screen mode.
+ */
+ public static void lockOrientation(Activity activity) {
+ Display display = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE))
+ .getDefaultDisplay();
+ int rotation = display.getRotation();
+ int tempOrientation = activity.getResources().getConfiguration().orientation;
+ int orientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+ switch (tempOrientation) {
+ case Configuration.ORIENTATION_LANDSCAPE: {
+ if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ } else {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ }
+ break;
+ }
+ case Configuration.ORIENTATION_PORTRAIT: {
+ if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_270) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ } else {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ }
+ break;
+ }
+ }
+ activity.setRequestedOrientation(orientation);
+ }
+
+ /**
+ * Unlocks the device window in user defined screen mode.
+ */
+ public static void unlockOrientation(Activity activity) {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
+ }
+
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java
index 5a314ad0b..eabbf83b8 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java
@@ -66,21 +66,24 @@ public class ParcelableFileCache<E extends Parcelable> {
File tempFile = new File(mContext.getCacheDir(), mFilename);
- DataOutputStream oos = new DataOutputStream(new FileOutputStream(tempFile));
- oos.writeInt(numEntries);
+ DataOutputStream oos = new DataOutputStream(new FileOutputStream(tempFile));
- while (it.hasNext()) {
- Parcel p = Parcel.obtain(); // creating empty parcel object
- p.writeParcelable(it.next(), 0); // saving bundle as parcel
- byte[] buf = p.marshall();
- oos.writeInt(buf.length);
- oos.write(buf);
- p.recycle();
+ try {
+ oos.writeInt(numEntries);
+
+ while (it.hasNext()) {
+ Parcel p = Parcel.obtain(); // creating empty parcel object
+ p.writeParcelable(it.next(), 0); // saving bundle as parcel
+ byte[] buf = p.marshall();
+ oos.writeInt(buf.length);
+ oos.write(buf);
+ p.recycle();
+ }
+ } finally {
+ oos.close();
}
- oos.close();
-
}
/**
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableHashMap.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableHashMap.java
new file mode 100644
index 000000000..fa4081acc
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableHashMap.java
@@ -0,0 +1,63 @@
+package org.sufficientlysecure.keychain.util;
+
+
+import java.util.HashMap;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+
+import org.sufficientlysecure.keychain.KeychainApplication;
+
+
+public class ParcelableHashMap <K extends Parcelable, V extends Parcelable> implements Parcelable {
+
+ HashMap<K,V> mInner;
+
+ public ParcelableHashMap(HashMap<K,V> inner) {
+ mInner = inner;
+ }
+
+ protected ParcelableHashMap(@NonNull Parcel in) {
+ mInner = new HashMap<>();
+ ClassLoader loader = KeychainApplication.class.getClassLoader();
+
+ int num = in.readInt();
+ while (num-- > 0) {
+ K key = in.readParcelable(loader);
+ V val = in.readParcelable(loader);
+ mInner.put(key, val);
+ }
+ }
+
+ public HashMap<K,V> getMap() {
+ return mInner;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mInner.size());
+ for (HashMap.Entry<K,V> entry : mInner.entrySet()) {
+ parcel.writeParcelable(entry.getKey(), 0);
+ parcel.writeParcelable(entry.getValue(), 0);
+ }
+ }
+
+ public static final Creator<ParcelableHashMap> CREATOR = new Creator<ParcelableHashMap>() {
+ @Override
+ public ParcelableHashMap createFromParcel(Parcel in) {
+ return new ParcelableHashMap(in);
+ }
+
+ @Override
+ public ParcelableHashMap[] newArray(int size) {
+ return new ParcelableHashMap[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+}
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..7e788d04c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+ }
+ /*
+ * InetSocketAddress.createUnresolved so we can use this method even in the main thread
+ * (no network call)
+ */
+ return new Proxy(mProxyType, InetSocketAddress.createUnresolved(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 303687315..4ef215036 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.service.KeyserverSyncAdapterService;
+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) {
@@ -182,6 +200,142 @@ public class Preferences {
editor.commit();
}
+ public void setFilesUseCompression(boolean compress) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Pref.FILE_USE_COMPRESSION, compress);
+ editor.commit();
+ }
+
+ public boolean getFilesUseCompression() {
+ return mSharedPreferences.getBoolean(Pref.FILE_USE_COMPRESSION, true);
+ }
+
+ public void setTextUseCompression(boolean compress) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Pref.TEXT_USE_COMPRESSION, compress);
+ editor.commit();
+ }
+
+ public boolean getTextUseCompression() {
+ 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();
+ editor.putBoolean(Pref.USE_ARMOR, useArmor);
+ editor.commit();
+ }
+
+ public boolean getUseArmor() {
+ return mSharedPreferences.getBoolean(Pref.USE_ARMOR, false);
+ }
+
+ public void setEncryptFilenames(boolean encryptFilenames) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Pref.ENCRYPT_FILENAMES, encryptFilenames);
+ editor.commit();
+ }
+
+ public boolean getEncryptFilenames() {
+ 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 = Pref.ProxyType.TYPE_HTTP;
+ final String typeSocks = Pref.ProxyType.TYPE_SOCKS;
+
+ String type = mSharedPreferences.getString(Pref.PROXY_TYPE, typeHttp);
+
+ switch (type) {
+ case typeHttp:
+ return Proxy.Type.HTTP;
+ case typeSocks:
+ return Proxy.Type.SOCKS;
+ default: // 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(false, true, 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);
+ }
+ }
+
+ // cloud prefs
+
public CloudSearchPrefs getCloudSearchPrefs() {
return new CloudSearchPrefs(mSharedPreferences.getBoolean(Pref.SEARCH_KEYSERVER, true),
mSharedPreferences.getBoolean(Pref.SEARCH_KEYBASE, true),
@@ -205,7 +359,39 @@ public class Preferences {
}
}
- public void updatePreferences() {
+ // experimental prefs
+
+ public void setExperimentalEnableWordConfirm(boolean enableWordConfirm) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Pref.EXPERIMENTAL_ENABLE_WORD_CONFIRM, enableWordConfirm);
+ editor.commit();
+ }
+
+ public boolean getExperimentalEnableWordConfirm() {
+ return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_WORD_CONFIRM, false);
+ }
+
+ public void setExperimentalEnableLinkedIdentities(boolean enableLinkedIdentities) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES, enableLinkedIdentities);
+ editor.commit();
+ }
+
+ public boolean getExperimentalEnableLinkedIdentities() {
+ return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES, false);
+ }
+
+ public void setExperimentalEnableKeybase(boolean enableKeybase) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Pref.EXPERIMENTAL_ENABLE_KEYBASE, enableKeybase);
+ editor.commit();
+ }
+
+ public boolean getExperimentalEnableKeybase() {
+ return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_KEYBASE, false);
+ }
+
+ public void upgradePreferences(Context context) {
if (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0) !=
Constants.Defaults.PREF_VERSION) {
switch (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0)) {
@@ -239,6 +425,14 @@ public class Preferences {
}
// fall through
case 4: {
+ setTheme(Constants.Pref.Theme.DEFAULT);
+ }
+ // fall through
+ case 5: {
+ KeyserverSyncAdapterService.enableKeyserverSync(context);
+ }
+ // fall through
+ case 6: {
}
}
@@ -248,4 +442,9 @@ public class Preferences {
.commit();
}
}
+
+ public void clear() {
+ mSharedPreferences.edit().clear().commit();
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java
index 120b84a3b..0297d149c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java
@@ -91,6 +91,7 @@ public class ShareHelper {
// Create chooser with only one Intent in it
Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title);
// append all other Intents
+ // TODO this line looks wrong?!
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{}));
return chooserIntent;
}
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..d85ad9128
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java
@@ -0,0 +1,463 @@
+/* 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.app.Activity;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+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 android.text.TextUtils;
+
+import org.sufficientlysecure.keychain.Constants;
+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.ui.util.ThemeChanger;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Preferences;
+
+import java.util.List;
+
+/**
+ * This class is taken from the NetCipher library: https://github.com/guardianproject/NetCipher/
+ */
+public class OrbotHelper {
+
+ public interface DialogActions {
+ void onOrbotStarted();
+
+ void onNeutralButton();
+
+ void onCancel();
+ }
+
+ private final static int REQUEST_CODE_STATUS = 100;
+
+ public final static String ORBOT_PACKAGE_NAME = "org.torproject.android";
+ public final static String ORBOT_MARKET_URI = "market://details?id=" + ORBOT_PACKAGE_NAME;
+ public final static String ORBOT_FDROID_URI = "https://f-droid.org/repository/browse/?fdid="
+ + ORBOT_PACKAGE_NAME;
+ public final static String ORBOT_PLAY_URI = "https://play.google.com/store/apps/details?id="
+ + ORBOT_PACKAGE_NAME;
+
+ /**
+ * A request to Orbot to transparently start Tor services
+ */
+ public final static String ACTION_START = "org.torproject.android.intent.action.START";
+ /**
+ * {@link Intent} send by Orbot with {@code ON/OFF/STARTING/STOPPING} status
+ */
+ public final static String ACTION_STATUS = "org.torproject.android.intent.action.STATUS";
+ /**
+ * {@code String} that contains a status constant: {@link #STATUS_ON},
+ * {@link #STATUS_OFF}, {@link #STATUS_STARTING}, or
+ * {@link #STATUS_STOPPING}
+ */
+ public final static String EXTRA_STATUS = "org.torproject.android.intent.extra.STATUS";
+ /**
+ * A {@link String} {@code packageName} for Orbot to direct its status reply
+ * to, used in {@link #ACTION_START} {@link Intent}s sent to Orbot
+ */
+ public final static String EXTRA_PACKAGE_NAME = "org.torproject.android.intent.extra.PACKAGE_NAME";
+
+ /**
+ * All tor-related services and daemons are stopped
+ */
+ @SuppressWarnings("unused") // we might use this later, sent by Orbot
+ public final static String STATUS_OFF = "OFF";
+ /**
+ * All tor-related services and daemons have completed starting
+ */
+ public final static String STATUS_ON = "ON";
+ @SuppressWarnings("unused") // we might use this later, sent by Orbot
+ public final static String STATUS_STARTING = "STARTING";
+ @SuppressWarnings("unused") // we might use this later, sent by Orbot
+ public final static String STATUS_STOPPING = "STOPPING";
+ /**
+ * The user has disabled the ability for background starts triggered by
+ * apps. Fallback to the old Intent that brings up Orbot.
+ */
+ public final static String STATUS_STARTS_DISABLED = "STARTS_DISABLED";
+
+ public final static String ACTION_START_TOR = "org.torproject.android.START_TOR";
+ /**
+ * request code used to start tor
+ */
+ public final static int START_TOR_RESULT = 0x9234;
+
+ private final static String FDROID_PACKAGE_NAME = "org.fdroid.fdroid";
+ private final static String PLAY_PACKAGE_NAME = "com.android.vending";
+
+ private OrbotHelper() {
+ // only static utility methods, do not instantiate
+ }
+
+ public static boolean isOrbotRunning(Context context) {
+ int procId = TorServiceUtils.findProcessId(context);
+
+ return (procId != -1);
+ }
+
+ public static boolean isOrbotInstalled(Context context) {
+ return isAppInstalled(context, ORBOT_PACKAGE_NAME);
+ }
+
+ private static boolean isAppInstalled(Context context, String uri) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * First, checks whether Orbot is installed, then checks whether Orbot is
+ * running. If Orbot is installed and not running, then an {@link Intent} is
+ * sent to request Orbot to start, which will show the main Orbot screen.
+ * The result will be returned in
+ * {@link Activity#onActivityResult(int requestCode, int resultCode, Intent data)}
+ * with a {@code requestCode} of {@code START_TOR_RESULT}
+ *
+ * @param activity the {@link Activity} that gets the
+ * {@code START_TOR_RESULT} result
+ * @return whether the start request was sent to Orbot
+ */
+ public static boolean requestShowOrbotStart(Activity activity) {
+ if (OrbotHelper.isOrbotInstalled(activity)) {
+ if (!OrbotHelper.isOrbotRunning(activity)) {
+ Intent intent = getShowOrbotStartIntent();
+ activity.startActivityForResult(intent, START_TOR_RESULT);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static Intent getShowOrbotStartIntent() {
+ Intent intent = new Intent(ACTION_START_TOR);
+ intent.setPackage(ORBOT_PACKAGE_NAME);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ /**
+ * First, checks whether Orbot is installed. If Orbot is installed, then a
+ * broadcast {@link Intent} is sent to request Orbot to start transparently
+ * in the background. When Orbot receives this {@code Intent}, it will
+ * immediately reply to this all with its status via an
+ * {@link #ACTION_STATUS} {@code Intent} that is broadcast to the
+ * {@code packageName} of the provided {@link Context} (i.e.
+ * {@link Context#getPackageName()}.
+ *
+ * @param context the app {@link Context} will receive the reply
+ * @return whether the start request was sent to Orbot
+ */
+ public static boolean requestStartTor(Context context) {
+ if (OrbotHelper.isOrbotInstalled(context)) {
+ Log.i("OrbotHelper", "requestStartTor " + context.getPackageName());
+ Intent intent = getOrbotStartIntent();
+ intent.putExtra(EXTRA_PACKAGE_NAME, context.getPackageName());
+ context.sendBroadcast(intent);
+ return true;
+ }
+ return false;
+ }
+
+ public static Intent getOrbotStartIntent() {
+ Intent intent = new Intent(ACTION_START);
+ intent.setPackage(ORBOT_PACKAGE_NAME);
+ return intent;
+ }
+
+ public static Intent getOrbotInstallIntent(Context context) {
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(ORBOT_MARKET_URI));
+
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);
+
+ String foundPackageName = null;
+ for (ResolveInfo r : resInfos) {
+ Log.i("OrbotHelper", "market: " + r.activityInfo.packageName);
+ if (TextUtils.equals(r.activityInfo.packageName, FDROID_PACKAGE_NAME)
+ || TextUtils.equals(r.activityInfo.packageName, PLAY_PACKAGE_NAME)) {
+ foundPackageName = r.activityInfo.packageName;
+ break;
+ }
+ }
+
+ if (foundPackageName == null) {
+ intent.setData(Uri.parse(ORBOT_FDROID_URI));
+ } else {
+ intent.setPackage(foundPackageName);
+ }
+ return intent;
+ }
+
+ /**
+ * hack to get around the fact that PreferenceActivity still supports only android.app.DialogFragment
+ */
+ 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 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);
+ }
+
+ /**
+ * 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(context)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * checks if Tor is enabled and if it is, that Orbot is installed and running. Generates appropriate dialogs.
+ *
+ * @param middleButton resourceId of string to display as the middle button of install and enable dialogs
+ * @param proxyPrefs proxy preferences used to determine if Tor is required to be started
+ * @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 DialogActions dialogActions,
+ Preferences.ProxyPrefs proxyPrefs,
+ final 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:
+ dialogActions.onNeutralButton();
+ break;
+ case SupportInstallDialogFragment.MESSAGE_DIALOG_DISMISSED:
+ // both install and cancel buttons mean we don't go ahead with an
+ // operation, so it's okay to cancel
+ dialogActions.onCancel();
+ break;
+ }
+ }
+ };
+
+ OrbotHelper.getInstallDialogFragmentWithThirdButton(
+ new Messenger(ignoreTorHandler),
+ middleButton
+ ).show(fragmentActivity.getSupportFragmentManager(), "OrbotHelperOrbotInstallDialog");
+
+ return false;
+ } else if (!OrbotHelper.isOrbotRunning(fragmentActivity)) {
+
+ final Handler dialogHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case OrbotStartDialogFragment.MESSAGE_MIDDLE_BUTTON:
+ dialogActions.onNeutralButton();
+ break;
+ case OrbotStartDialogFragment.MESSAGE_DIALOG_CANCELLED:
+ dialogActions.onCancel();
+ break;
+ case OrbotStartDialogFragment.MESSAGE_ORBOT_STARTED:
+ dialogActions.onOrbotStarted();
+ break;
+ }
+ }
+ };
+
+ new SilentStartManager() {
+
+ @Override
+ protected void onOrbotStarted() {
+ dialogActions.onOrbotStarted();
+ }
+
+ @Override
+ protected void onSilentStartDisabled() {
+ getOrbotStartDialogFragment(new Messenger(dialogHandler), middleButton)
+ .show(fragmentActivity.getSupportFragmentManager(),
+ "OrbotHelperOrbotStartDialog");
+ }
+ }.startOrbotAndListen(fragmentActivity, true);
+
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public static boolean putOrbotInRequiredState(DialogActions dialogActions,
+ FragmentActivity fragmentActivity) {
+ return putOrbotInRequiredState(R.string.orbot_ignore_tor,
+ dialogActions,
+ Preferences.getPreferences(fragmentActivity).getProxyPrefs(),
+ fragmentActivity);
+ }
+
+ /**
+ * will attempt a silent start, which if disabled will fallback to the
+ * {@link #requestShowOrbotStart(Activity) requestShowOrbotStart} method, which returns the
+ * result in {@link Activity#onActivityResult(int requestCode, int resultCode, Intent data)}
+ * with a {@code requestCode} of {@code START_TOR_RESULT}, which will have to be implemented by
+ * activities wishing to respond to a change in Orbot state.
+ */
+ public static void bestPossibleOrbotStart(final DialogActions dialogActions,
+ final Activity activity,
+ boolean showProgress) {
+ new SilentStartManager() {
+
+ @Override
+ protected void onOrbotStarted() {
+ dialogActions.onOrbotStarted();
+ }
+
+ @Override
+ protected void onSilentStartDisabled() {
+ requestShowOrbotStart(activity);
+ }
+ }.startOrbotAndListen(activity, showProgress);
+ }
+
+ /**
+ * base class for listening to silent orbot starts. Also handles display of progress dialog.
+ */
+ public static abstract class SilentStartManager {
+
+ private ProgressDialog mProgressDialog;
+
+ public void startOrbotAndListen(final Context context, final boolean showProgress) {
+ Log.d(Constants.TAG, "starting orbot listener");
+ if (showProgress) {
+ showProgressDialog(context);
+ }
+
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getStringExtra(OrbotHelper.EXTRA_STATUS)) {
+ case OrbotHelper.STATUS_ON:
+ context.unregisterReceiver(this);
+ // generally Orbot starts working a little after this status is received
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (showProgress) {
+ mProgressDialog.dismiss();
+ }
+ onOrbotStarted();
+ }
+ }, 1000);
+ break;
+ case OrbotHelper.STATUS_STARTS_DISABLED:
+ context.unregisterReceiver(this);
+ if (showProgress) {
+ mProgressDialog.dismiss();
+ }
+ onSilentStartDisabled();
+ break;
+
+ }
+ Log.d(Constants.TAG, "Orbot silent start broadcast: " +
+ intent.getStringExtra(OrbotHelper.EXTRA_STATUS));
+ }
+ };
+ context.registerReceiver(receiver, new IntentFilter(OrbotHelper.ACTION_STATUS));
+
+ requestStartTor(context);
+ }
+
+ private void showProgressDialog(Context context) {
+ mProgressDialog = new ProgressDialog(ThemeChanger.getDialogThemeWrapper(context));
+ mProgressDialog.setMessage(context.getString(R.string.progress_starting_orbot));
+ mProgressDialog.setCancelable(false);
+ mProgressDialog.show();
+ }
+
+ protected abstract void onOrbotStarted();
+
+ protected abstract void onSilentStartDisabled();
+ }
+}
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..2638f8cd5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java
@@ -0,0 +1,156 @@
+/* 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 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 {
+
+ private final static String TAG = "TorUtils";
+
+ public final static String SHELL_CMD_PS = "ps";
+ public final static String SHELL_CMD_PIDOF = "pidof";
+
+ public static int findProcessId(Context context) {
+ String dataPath = context.getFilesDir().getParentFile().getParentFile().getAbsolutePath();
+ String command = dataPath + "/" + OrbotHelper.ORBOT_PACKAGE_NAME + "/app_bin/tor";
+ 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(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;
+
+ }
+} \ No newline at end of file